Получить индекс дочернего узла


в прямом 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 70

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>