获取子节点索引


125

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

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


2
不好意思,我是完全傻瓜吗?这里有很多看似学得好的答案,但是要获得所有子节点,您不需要做parent.childNodes,而不是parent.children?后者仅列出了Elements,但不包括特定的Text节点...这里的某些答案,例如using previousSibling,是基于使用所有子节点的,而其他答案仅受Elements 子节点的困扰。(!)
mike rodent

@mikerodent我不记得我最初问这个问题时的目的是什么,但这是我所不知道的关键细节。除非您小心,.childNodes否则绝对应使用代替.children。正如您所指出的,前2个发布的答案将给出不同的结果。
所有工人都

当计划在1000多个节点上进行数千次查找时,然后将信息附加到该节点(例如,通过child.dataset)。目标是将O(n)或O(n ^ 2)算法转换为O(1)算法。不利之处在于,如果定期添加和删除节点,则也必须更新连接到节点的关联位置信息,这可能不会导致任何性能提升。偶尔的迭代没什么大不了的(例如单击处理程序),但是重复的迭代是有问题的(例如,mousemove)。
CubicleSoft

Answers:


122

您可以使用该previousSibling属性遍历兄弟姐妹,直到返回null并计算遇到的兄弟姐妹数:

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

请注意,在Java之类的语言中,有一个getPreviousSibling()函数,但是在JS中,它已经成为一个属性- previousSibling


2
是的 不过,您在文本中保留了一个getPreviousSibling()。
蒂姆·唐

7
这种方法需要相同的迭代次数才能确定子索引,因此我看不出它将有多快。
迈克尔

31
一线版本:for (var i=0; (node=node.previousSibling); i++);
Scott Miles

2
@sfarbota Javascript不知道块作用域,因此i可以访问。
A1rPun 2014年

3
@nepdev那将是因为之间的差异.previousSibling.previousElementSibling。前者命中文本节点,后者则没有。
abluejelly

132

我很喜欢indexOf为此使用。因为indexOfon Array.prototypeparent.children是a NodeList,所以您必须使用call();It有点难看,但是它是一个线性代码,并且使用任何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);

7
var index = [] .indexOf.call(child.parentNode.children,child);
cuixiping 2014年

23
Fwiw使用[]每次运行该代码时都会创建一个Array实例,与使用相比,这对于内存和GC效率较低Array.prototype
Scott Miles

@ScottMiles我想请您解释一下吗?不会[]清除内存中的垃圾值吗?
mrReiha

14
为了评估[].indexOf引擎,必须创建一个数组实例来访问indexOf原型上的实现。实例本身未使用(它执行GC,这不是泄漏,只是浪费周期)。Array.prototype.indexOf直接访问该实现,而无需分配匿名实例。在几乎所有情况下,这种差异将是微不足道的,因此坦率地说,它可能不值得关注。
Scott Miles

3
小心IE中的错误!Internet Explorer 6、7和8支持它,但错误地包括Comment节点。来源” developer.mozilla.org/en-US/docs/Web/API/ParentNode/…–
Luckylooke

101

ES6:

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

说明:

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

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

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


2
到目前为止,最优雅的解决方案:)
阿米特·萨克森纳

1
但仅适用于Chrome 45和Firefox 32,而完全不适用于Internet Explorer。
彼得

Internet Explorer仍然存在吗?Just Jock ..好的,所以您需要一个polyfill来制作Array.fromInternet Explorer上的作品
Abdennour TOUMI

9
根据MDN的说法,调用Array.from() creates a new Array instance from an array-like or iterable object.创建一个新的数组实例以查找索引可能不具有内存或GC效率,这取决于操作的频率,在这种情况下,如已接受的答案所述,迭代会更多。理想。
TheDarkIn1978 '17

1
@ TheDarkIn1978我知道代码的优雅与应用程序的性能之间存在折衷off
Abdennour TOUMI

38

ES—Shorter

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

传播算子是实现此目的的捷径


这是一个有趣的运算符。
所有工人都

e.parentElement.childNodes和之间有什么区别e.parentNode.children
mesqueeb

4
childNodes也包括文本节点
philipp '18

使用打字稿,您会得到 Type 'NodeListOf<ChildNode>' must have a '[Symbol.iterator]()' method that returns an iterator.ts(2488)
ZenVentzi

9

添加(为安全起见)element.getParentIndex():

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

1
Web开发的痛苦有一个原因:前缀跳跃的开发者。为什么不做if (!Element.prototype.getParentIndex) Element.prototype.getParentIndex = function(){ /* code here */ }呢?无论如何,如果将来将其实现为标准,则可能会像getter这样实现element.parentIndex。因此,我想说最好的方法是if(!Element.prototype.getParentIndex) Element.prototype.getParentIndex=Element.prototype.parentIndex?function() {return this.parentIndex}:function() {return Array.prototype.indexOf.call(this.parentNode.children, this)}
杰克·吉芬

3
因为将来的getParentIndex()签名可能与您的实现不同。
mikemaccana

7

我假设给定一个元素,其所有子元素均在文档中顺序排列,最快的方法应该是进行二进制搜索,比较元素在文档中的位置。但是,正如结论中所介绍的,该假设被拒绝了。您拥有的元素越多,性能潜力越大。例如,如果您有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() {
        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万个元素上(可能会使某些移动浏览器崩溃,请注意!):

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

二进制搜索

向后(`lastIndexOf`)线性搜索

前向(`indexOf`)线性搜索

PreviousElement同级计数器搜索

计算获得上级索引的PreviousElementSiblings的数量。

没有搜寻

对于基准测试,如果浏览器优化了搜索结果,测试结果将是什么。

阴谋

但是,在Chrome中查看结果后,结果与预期相反。dumber正向线性搜索比二进制搜索快187 ms,令人吃惊3850%。显然,Chrome以某种方式神奇地胜过了console.assert它,并对其进行了优化,或者(更乐观地)Chrome内部使用了DOM的数字索引系统,并且该内部索引系统通过在对象Array.prototype.indexOf上使用时的优化而暴露出来HTMLCollection


高效,但不切实际。
applemonkey496

3

当节点具有大量同级节点时,使用二进制搜索算法来提高性能。

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

不起作用 我不得不指出IE版本和“其他浏览器”版本会计算出不同的结果。“其他浏览器”技术按预期工作,在父节点下获得第n个位置,但是IE技术“按源顺序检索对象的顺序位置,因为该对象出现在文档的所有集合中”(msdn.microsoft .com / en-gb / library / ie / ms534635(v = vs.85).aspx)。例如,我使用“ IE”技术获得了126,然后使用其他技术获得了4。
Christopher Bull

1

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

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

    return position;
}

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

1
您能解释一下您为什么从扩展Element.prototype吗?这些函数看起来很有用,但我不知道这些函数的作用(即使命名很明显)。
A1rPun

@extend Element.prototype原因是相似的... 4个elemen.children,element.parentNode等...所以您寻址element.siblings ....的方式相同。group方法有点复杂,因为我要扩展通过相同的nodeType和具有相同的属性甚至没有相同祖先的
元素

我知道什么是原型扩展,但我想知道如何使用您的代码。el.group.value()??。我的第一条评论是提高您的回答质量。
A1rPun 2015年

group和siblings方法返回具有已建立dom元素的Array .. ....感谢您的评论以及评论的原因
bortunac 2015年

很优雅,但也很慢。
杰克·吉芬


-1
<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>
        function childIndex(e){
            let i = 0;
            while (e.parentNode.children[i] != e) i++;
            alert('child index '+i);
        }
    </script>
</body>

您在这里不需要jQuery。
Vitaly Zdanevich
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.