Получить индекс дочернего узла
в прямом javascript (т. е. без расширений, таких как jQuery и т. д.), есть ли способ определить индекс дочернего узла внутри его родительского узла без итерации и сравнения всех дочерних узлов?
например,
var child = document.getElementById('my_element');
var parent = child.parentNode;
var childNodes = parent.childNodes;
var count = childNodes.length;
var child_index;
for (var i = 0; i < count; ++i) {
if (child === childNodes[i]) {
child_index = i;
break;
}
}
есть ли лучший способ определить индекс ребенка?
9 ответов:
можно использовать
previousSibling
свойство для итерации обратно через братьев и сестер, пока вы не вернетесьnull
и посчитать, сколько братьев и сестер вы столкнулись:var i = 0; while( (child = child.previousSibling) != null ) i++; //at the end i will contain the index.
обратите внимание, что в таких языках, как Java есть
getPreviousSibling()
функция, однако в JS это стало свойством --previousSibling
.
Я полюбил использовать
indexOf
для этого. Потому чтоindexOf
наArray.prototype
иparent.children
этоNodeList
, вы должны использоватьcall();
это довольно уродливо, но это один лайнер и использует функции, с которыми любой JavaScript-разработчик должен быть знаком в любом случае.var child = document.getElementById('my_element'); var parent = child.parentNode; // The equivalent of parent.children.indexOf(child) var index = Array.prototype.indexOf.call(parent.children, child);
ES6:
Array.from(element.parentNode.children).indexOf(element)
объяснение :
element.parentNode.children
→ возвращает братьевelement
, в том числе и этот элемент.
Array.from
→ бросает конструкторchildren
доArray
объект
indexOf
→ вы можете подать заявкуindexOf
потому что теперь у вас есть
ES-короче
[...element.parentNode.children].indexOf(element);
оператор spread является ярлыком для этого
добавление элемента (с префиксом для безопасности).getParentIndex ():
Element.prototype.PREFIXgetParentIndex = function() { return Array.prototype.indexOf.call(this.parentNode.children, this); }
использовать алгоритм бинарного поиска для улучшения производительности, когда узел имеет большое количество братьев и сестер.
function getChildrenIndex(ele){ //IE use Element.sourceIndex if(ele.sourceIndex){ var eles = ele.parentNode.children; var low = 0, high = eles.length-1, mid = 0; var esi = ele.sourceIndex, nsi; //use binary search algorithm while (low <= high) { mid = (low + high) >> 1; nsi = eles[mid].sourceIndex; if (nsi > esi) { high = mid - 1; } else if (nsi < esi) { low = mid + 1; } else { return mid; } } } //other browsers var i=0; while(ele = ele.previousElementSibling){ i++; } return i; }
самый быстрый способ - сделать двоичный поиск, сравнивая позиции документов элементов. Чем больше элементов, тем больше потенциал для исполнения. Например, если бы у вас было 256 элементов, то (оптимально) вам нужно было бы проверить только 16 из них! Для 65536, только 256! Производительность растет до мощности 2! См. дополнительные цифры / статистику. Посетите Википедия.
(function(constructor){ 'use strict'; Object.defineProperty(constructor.prototype, 'parentIndex', { get: function() { var searchParent = this.parentElement; if (!searchParent) return -1; var searchArray = searchParent.children, thisOffset = this.offsetTop, stop = searchArray.length, p = 0, delta = 0; while (searchArray[p] !== this) { if (searchArray[p] > this) stop = p + 1, p -= delta; delta = (stop - p) >>> 1; p += delta; } return p; } }); })(window.Element || Node);
затем, как вы используете его, получая 'parentIndex' свойство любого элемента. Например, ознакомьтесь со следующей демонстрацией.
(function(constructor){ 'use strict'; Object.defineProperty(constructor.prototype, 'parentIndex', { get: function() {debugger; var searchParent = this.parentNode; if (searchParent === null) return -1; var childElements = searchParent.children, lo = -1, mi, hi = childElements.length; while (1 + lo !== hi) { mi = (hi + lo) >> 1; if (!(this.compareDocumentPosition(childElements[mi]) & 0x2)) { hi = mi; continue; } lo = mi; } return childElements[hi] === this ? hi : -1; } }); })(window.Element || Node); output.textContent = document.body.parentIndex; output2.textContent = document.documentElement.parentIndex;
Body parentIndex is <b id="output"></b><br /> documentElements parentIndex is <b id="output2"></b>
ограничения
- реализация этого решения не будет работать в IE8 и ниже.
Binary VS Linear Search на 200 тыс. элементов (может произойти сбой некоторых мобильных браузеров, будьте осторожны!):
- в этом тесте мы увидим, сколько времени требуется для линейного поиска, чтобы найти середину элемент против двоичного поиска. Почему именно средний элемент? Поскольку он находится в среднем местоположении всех других местоположений, поэтому он лучше всего представляет все возможные местоположения.
Бинарный Поиск
(function(constructor){ 'use strict'; Object.defineProperty(constructor.prototype, 'parentIndex', { get: function() {debugger; var searchParent = this.parentNode; if (searchParent === null) return -1; var childElements = searchParent.children, lo = -1, mi, hi = childElements.length; while (1 + lo !== hi) { mi = (hi + lo) >> 1; if (!(this.compareDocumentPosition(childElements[mi]) & 0x2)) { hi = mi; continue; } lo = mi; } return childElements[hi] === this ? hi : -1; } }); })(window.Element || Node); test.innerHTML = '<div> </div> '.repeat(200e+3); // give it some time to think: setTimeout(function(){ var child=test.children.item(99e+3); var start=performance.now(); for (var i=200; i--; ) console.assert( (child=child.nextElementSibling).parentIndexBinarySearch ); var end=performance.now(); setTimeout(function(){ output.textContent = 'It took the binary search ' + ((end-start)*10).toFixed(2) + 'ms to find the 999 thousandth to 101 thousandth children in an element with 200 thousand children.'; test.innerHTML = ''; }, 125); }, 125);
<output id=output> </output><br /> <div id=test style=visibility:hidden></div> <style>body{overflow:hidden}</style>
обратный (`lastIndexOf') линейный поиск
(function(t){"use strict";var e=Array.prototype.lastIndexOf;Object.defineProperty(t.prototype,"parentIndexLinearSearch",{get:function(){return e.call(t,this)}})})(window.Element||Node); test.innerHTML = '<div> </div> '.repeat(200e+3); // give it some time to think: setTimeout(function(){ var child=test.children.item(99e+3); var start=performance.now(); for (var i=2000; i--; ) console.assert( (child=child.nextElementSibling).parentIndexLinearSearch ); var end=performance.now(); setTimeout(function(){ output.textContent = 'It took the linear search ' + (end-start).toFixed(2) + 'ms to find the 999 thousandth to 101 thousandth children in an element with 200 thousand children.'; test.innerHTML = ''; }, 125); }, 125);
<output id=output> </output><br /> <div id=test style=visibility:hidden></div> <style>body{overflow:hidden}</style>
вперед (`indexOf') линейный поиск
линейный поиск, за исключением перехода вперед назад вместо назад вперед.
(function(t){"use strict";var e=Array.prototype.indexOf;Object.defineProperty(t.prototype,"parentIndexLinearSearch",{get:function(){return e.call(t,this)}})})(window.Element||Node); test.innerHTML = '<div> </div> '.repeat(200e+3); // give it some time to think: setTimeout(function(){ var child = test.children.item(99e+3); var start=performance.now(); for (var i=2000; i--; ) console.assert( (child=child.nextElementSibling).parentIndexLinearSearch ); var end=performance.now(); setTimeout(function(){ output.textContent = 'It took the linear search ' + (end-start).toFixed(2) + 'ms to find the 999 thousandth to 101 thousandth children in an element with 200 thousand children.'; test.innerHTML = ''; }, 125); }, 125);
<output id=output> </output><br /> <div id=test style=visibility:hidden></div> <style>body{overflow:hidden}</style>
однако, после просмотра результатов в Chrome, результаты противоположны тому, что ожидалось. Более тупой вперед линейный поиск был удивительным 187 МС, 3850%, быстрее, чем двоичный поиск. Видимо, хром каким-то волшебным образом перехитрил
console.assert
и оптимизировал его прочь, или (более оптимистично) Chrome внутренне использует численную систему индексирования для DOM, и эта внутренняя система индексирования выставляется через оптимизации, применяемые кArray.prototype.indexOf
при использовании
Object.defineProperties(Element.prototype,{ group : { value: function (str, context) { // str is valid css selector like :not([attr_name]) or .class_name var t = "to_select_siblings___"; var parent = context ? context : this.parentNode; parent.setAttribute(t, ''); var rez = document.querySelectorAll("[" + t + "] " + (context ? '' : ">") + this.nodeName + (str || "")).toArray(); parent.removeAttribute(t); return rez; } }, siblings: { value: function (str, context) { var rez=this.group(str,context); rez.splice(rez.indexOf(this), 1); return rez; } }, nth: { value: function(str,context){ return this.group(str,context).indexOf(this); } } }
Ex
/* html */ <ul id="the_ul"> <li></li> ....<li><li>....<li></li> </ul> /*js*/ the_ul.addEventListener("click", function(ev){ var foo=ev.target; foo.setAttribute("active",true); foo.siblings().map(function(elm){elm.removeAttribute("active")}); alert("a click on li" + foo.nth()); });
<body> <section> <section onclick="childIndex(this)">child a</section> <section onclick="childIndex(this)">child b</section> <section onclick="childIndex(this)">child c</section> </section> <script src="//code.jquery.com/jquery-1.11.3.min.js"></script> <script> function childIndex(e){ var i = 0; debugger while (e.parentNode.children[i] != e) i++; alert('child index '+i); } </script> </body>