获取子节点索引

use*_*450 100 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;
  }
}
Run Code Online (Sandbox Code Playgroud)

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

Kha*_*nna 112

我喜欢用indexOf它.因为它indexOf是on Array.prototype并且parent.children是a 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);
Run Code Online (Sandbox Code Playgroud)

  • Fwiw,使用`[]`每次运行该代码时都会创建一个Array实例,与使用`Array.prototype`相比,内存和GC效率较低. (21认同)
  • 要评估`[] .indexOf`,引擎必须创建一个数组实例,只是为了访问原型上的`indexOf`实现.实例本身未被使用(它做GC,它不是泄漏,它只是浪费周期).`Array.prototype.indexOf`直接访问该实现而不分配匿名实例.在几乎所有情况下差异都可以忽略不计,所以坦率地说,这可能不值得关注. (11认同)
  • var index = [] .indexOf.call(child.parentNode.children,child); (7认同)
  • 小心 IE 中的错误!Internet Explorer 6、7 和 8 支持它,但错误地包含注释节点。来源” https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/children#Browser_compatibility (3认同)
  • 在现代 javascript 中你可以使用 `[ ...parent.children ].indexOf ( child )` (2认同)

Liv*_*Liv 110

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

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

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

  • 一行版本:`for(var i = 0;(node = node.previousSibling); i ++);` (26认同)
  • 这种方法需要相同数量的迭代来确定子索引,所以我看不出它会如何更快. (7认同)
  • @nepdev那是因为`.previousSibling`和`.previousElementSibling`之间的区别.前者命中文本节点,后者则没有. (3认同)
  • 是的.你在文本中留下了一个getPreviousSibling(). (2认同)
  • @sfarbota Javascript 不知道块作用域,因此可以访问 `i`。 (2认同)
  • @ScottMiles为什么不`var i=0; while(child=child.previousSibling) i++;`? (2认同)

Abd*_*UMI 75

ES6:

Array.from(element.parentNode.children).indexOf(element)
Run Code Online (Sandbox Code Playgroud)

说明:

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

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

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

  • 根据MDN,调用[**Array.from()**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from)`创建一个新数组来自类似数组或可迭代对象的实例.创建一个新的数组实例只是为了找到索引可能不是内存或GC效率,这取决于操作的频率,在这种情况下迭代,如接受的答案中所解释的那样,将是更理想. (9认同)
  • 迄今为止最优雅的解决方案:) (4认同)
  • @TheDarkIn1978 我知道代码优雅和应用程序性能之间存在权衡 (2认同)

phi*_*ipp 33

ES-短

[...element.parentNode.children].indexOf(element);
Run Code Online (Sandbox Code Playgroud)

传播运算符是它的捷径

  • `childNodes`也包括文本节点 (4认同)

mik*_*ana 9

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

Element.prototype.PREFIXgetParentIndex = function() {
  return Array.prototype.indexOf.call(this.parentNode.children, this);
}
Run Code Online (Sandbox Code Playgroud)

  • 因为将来的`getParentIndex()`可能具有与实现不同的签名。 (3认同)

Jeh*_*Ahn 8

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

  • 请注意,对于“&lt;tr&gt;”元素,这将考虑表中可能还有的任何表标题。 (3认同)

1.2*_*tts 7

你能不能做这样的事情:

var index = Array.prototype.slice.call(element.parentElement.children).indexOf(element);
Run Code Online (Sandbox Code Playgroud)

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


Jac*_*fin 5

我假设给定一个元素,其所有子元素在文档上都按顺序排列,最快的方法应该是进行二进制搜索,比较元素在文档中的位置。但是,正如结论中所介绍的,该假设被拒绝了。您拥有的元素越多,性能的潜力就越大。例如,如果您有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);
Run Code Online (Sandbox Code Playgroud)

然后,使用它的方式是获取任何元素的'parentIndex'属性。例如,请查看以下演示。

(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);
Run Code Online (Sandbox Code Playgroud)
(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;
Run Code Online (Sandbox Code Playgroud)

局限性

  • 解决方案的此实现在IE8及以下版本中将不起作用。

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

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

二元搜寻

Body parentIndex is <b id="output"></b><br />
documentElements parentIndex is <b id="output2"></b>
Run Code Online (Sandbox Code Playgroud)
(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);
Run Code Online (Sandbox Code Playgroud)

向后(`lastIndexOf`)线性搜索

<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>
Run Code Online (Sandbox Code Playgroud)
(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);
});
Run Code Online (Sandbox Code Playgroud)

前向(`indexOf`)线性搜索

<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>
Run Code Online (Sandbox Code Playgroud)
(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);
});
Run Code Online (Sandbox Code Playgroud)

PreviousElement兄弟计数器搜索

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

<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>
Run Code Online (Sandbox Code Playgroud)
(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);
});
Run Code Online (Sandbox Code Playgroud)

没有搜寻

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

<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>
Run Code Online (Sandbox Code Playgroud)
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);
});
Run Code Online (Sandbox Code Playgroud)

阴谋

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