获取子节点索引

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

有没有更好的方法来确定孩子的索引?

191332 次浏览

您可以使用 previousSibling属性迭代返回到兄弟节点,直到返回 null并计算遇到了多少兄弟节点:

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

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

使用 前情提要兄弟姐妹忽略文本和注释节点。

当节点有大量的兄弟节点时,使用 二进制搜索算法可以提高性能。

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

添加一个(以安全为前缀) element. getParentIndex () :

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

我已经变得喜欢使用 indexOf为这一点。因为 indexOfArray.prototype上,而 parent.childrenNodeList,所以你必须使用 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);
<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>
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());
});

ES6:

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

说明:

  • 返回 element的兄弟,包括该元素。

  • children的构造函数强制转换为 Array对象

  • 可以应用 indexOf,因为现在有了一个 Array对象。

ES ー更短

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

传播操作符是一个快捷方式

低效二进制搜索的证明

我假设给定一个元素,其所有子元素在文档中按顺序排列,最快的方法应该是进行二进制搜索,比较元素的文档位置。然而,正如结论中介绍的那样,这个假设被否定了。元素越多,性能潜力就越大。例如,如果您有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 线性搜索200,000个元素(可能会导致一些移动浏览器崩溃,小心!) :

  • 在这个测试中,我们将看到线性搜索需要多长时间才能找到中间元素 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>

兄弟姐妹反向搜索

计算 PreviousElement 兄弟姐妹的数量以获取双亲索引。

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

结论

然而,在 Chrome 浏览器中查看结果后,得到的结果却与预期相反。更笨的线性转发搜索是令人惊讶的187毫秒,3850% ,比二进制搜索快。显然,Chrome 以某种方式神奇地胜过了 console.assert并将其优化掉,或者(更乐观地说) Chrome 内部使用 DOM 的数字索引系统,而这种内部索引系统是通过在 HTMLCollection对象上使用时对 Array.prototype.indexOf的优化而暴露出来的。

你能这样做吗:

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

Https://developer.mozilla.org/en-us/docs/web/api/node/parentelement

我有一个文本节点的问题,它显示了错误的索引。

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


return position;
}

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

对我来说,这个代码更加清晰

const myElement = ...;
const index = [...document.body.children].indexOf(myElement);