获取子节点索引

时间:2011-05-06 15:55:21

标签: javascript dom

在直接的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;
  }
}

有没有更好的方法来确定孩子的指数?

12 个答案:

答案 0 :(得分:106)

您可以使用previousSibling属性迭代兄弟姐妹,直到您回到null并计算您遇到的兄弟姐妹数量:

var i = 0;
while( (child = child.previousSibling) != null ) 
  i++;
//at the end i will contain the index.

请注意,在Java等语言中,有一个getPreviousSibling()函数,但在JS中,这已成为属性 - previousSibling

答案 1 :(得分:102)

我喜欢用indexOf来做这件事。由于indexOf位于Array.prototypeparent.children位于NodeList,因此您必须使用call();这有点难看,但它是一个单行并使用任何功能无论如何,javascript dev应该熟悉。

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);

答案 2 :(得分:63)

ES6:

Array.from(element.parentNode.children).indexOf(element)

说明:

  • element.parentNode.children→返回element的兄弟,包括该元素。

  • Array.from→将children的构造函数转换为Array对象

  • indexOf→您可以申请indexOf,因为您现在拥有Array个对象。

答案 3 :(得分:33)

<强> ES-较短

[...element.parentNode.children].indexOf(element);

传播运营商是

的捷径

答案 4 :(得分:8)

添加(前缀为安全性)element.getParentIndex():

Element.prototype.PREFIXgetParentIndex = function() {
  return Array.prototype.indexOf.call(this.parentNode.children, this);
}

答案 5 :(得分:5)

我假设给定一个元素,其中所有子元素按顺序排列在文档上,最快的方法应该是进行二元搜索,比较元素的文档位置。但是,正如结论中所介绍的那样,假设被拒绝了。您拥有的元素越多,性能潜力就越大。例如,如果您有256个元素,那么(最佳)您只需要检查其中的16个元素!对于65536,只有256!性能增长到2的力量!查看更多数字/统计数据。访问Wikipedia

(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() {
        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及以下版本中无效。

二进制VS线性搜索20万个元素(可能会崩溃某些移动浏览器,请注意!):

  • 在此测试中,我们将看到线性搜索找到中间元素VS二进制搜索所需的时间。为什么中间元素?因为它位于所有其他位置的平均位置,所以它最好代表所有可能的位置。

二进制搜索

(function(constructor){
   'use strict';
    Object.defineProperty(constructor.prototype, 'parentIndexBinarySearch', {
      get: function() {
        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:
requestAnimationFrame(function(){
  var child=test.children.item(99.9e+3);
  var start=performance.now(), end=Math.round(Math.random());
  for (var i=200 + end; i-- !== end; )
    console.assert( test.children.item(
        Math.round(99.9e+3+i+Math.random())).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.remove();
    test = null; // free up reference
  }, 125);
}, 125);
<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>

向后(`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:
requestAnimationFrame(function(){
  var child=test.children.item(99e+3);
  var start=performance.now(), end=Math.round(Math.random());
  for (var i=2000 + end; i-- !== end; )
    console.assert( test.children.item(
        Math.round(99e+3+i+Math.random())).parentIndexLinearSearch );
  var end=performance.now();
  setTimeout(function(){
    output.textContent = 'It took the backwards linear search ' + (end-start).toFixed(2) + 'ms to find the 999 thousandth to 101 thousandth children in an element with 200 thousand children.';
    test.remove();
    test = null; // free up reference
  }, 125);
});
<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>

前锋(`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:
requestAnimationFrame(function(){
  var child=test.children.item(99e+3);
  var start=performance.now(), end=Math.round(Math.random());
  for (var i=2000 + end; i-- !== end; )
    console.assert( test.children.item(
        Math.round(99e+3+i+Math.random())).parentIndexLinearSearch );
  var end=performance.now();
  setTimeout(function(){
    output.textContent = 'It took the forwards linear search ' + (end-start).toFixed(2) + 'ms to find the 999 thousandth to 101 thousandth children in an element with 200 thousand children.';
    test.remove();
    test = null; // free up reference
  }, 125);
});
<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>

PreviousElementSibling Counter Search

计算PreviousElementSiblings的数量以获取parentIndex。

(function(constructor){
   'use strict';
    Object.defineProperty(constructor.prototype, 'parentIndexSiblingSearch', {
      get: function() {
        var i = 0, cur = this;
        do {
            cur = cur.previousElementSibling;
            ++i;
        } while (cur !== null)
        return i; //Returns 3
      }
    });
})(window.Element || Node);
test.innerHTML = '<div> </div> '.repeat(200e+3);
// give it some time to think:
requestAnimationFrame(function(){
  var child=test.children.item(99.95e+3);
  var start=performance.now(), end=Math.round(Math.random());
  for (var i=100 + end; i-- !== end; )
    console.assert( test.children.item(
        Math.round(99.95e+3+i+Math.random())).parentIndexSiblingSearch );
  var end=performance.now();
  setTimeout(function(){
    output.textContent = 'It took the PreviousElementSibling search ' + ((end-start)*20).toFixed(2) + 'ms to find the 999 thousandth to 101 thousandth children in an element with 200 thousand children.';
    test.remove();
    test = null; // free up reference
  }, 125);
});
<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>

无搜索

如果浏览器优化了搜索,则用于对测试结果进行基准测试。

test.innerHTML = '<div> </div> '.repeat(200e+3);
// give it some time to think:
requestAnimationFrame(function(){
  var start=performance.now(), end=Math.round(Math.random());
  for (var i=2000 + end; i-- !== end; )
    console.assert( true );
  var end=performance.now();
  setTimeout(function(){
    output.textContent = 'It took the no search ' + (end-start).toFixed(2) + 'ms to find the 999 thousandth to 101 thousandth children in an element with 200 thousand children.';
    test.remove();
    test = null; // free up reference
  }, 125);
});
<output id=output> </output><br />
<div id=test style=visibility:hidden></div>

The Conculsion

但是,在Chrome中查看结果后,结果与预期相反。数字转发线性搜索是一个令人惊讶的187毫秒,3850%,比二进制搜索更快。很明显,Chrome以某种方式神奇地超越了console.assert并对其进行了优化,或者(更乐观地)Chrome内部使用了数字索引系统来实现DOM,而这个内部索引系统通过应用于Array.prototype.indexOf的优化来公开。用于HTMLCollection对象。

答案 6 :(得分:3)

当节点具有大量兄弟节点时,使用binary search algorithm来提高性能。

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;
}

答案 7 :(得分:1)

我的文本节点有问题,它显示了错误的索引。这是修复它的版本。

function getChildNodeIndex(elem)
{   
    let position = 0;
    while ((elem = elem.previousSibling) != null)
    {
        if(elem.nodeType != Node.TEXT_NODE)
            position++;
    }

    return position;
}

答案 8 :(得分:0)

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);
    }
}
}

/* 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());
     });

答案 9 :(得分:0)

您可以这样做吗?

var index = Array.prototype.slice.call(element.parentElement.children).indexOf(element);

https://developer.mozilla.org/en-US/docs/Web/API/Node/parentElement

答案 10 :(得分:0)

如果您的元素是 <tr/><td/>,请使用 rowIndex/cellIndex 属性。

答案 11 :(得分:-2)

<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>