Javascript 获取子节点索引
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5913927/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Get child node index
提问by All Workers Are Essential
In straight up javascript (i.e., no extensions such as jQuery, etc.), is there a way to determine a child node's index inside of its parent node without iterating over and comparing all children nodes?
在直接的 javascript 中(即没有诸如 jQuery 等的扩展),有没有一种方法可以在不迭代和比较所有子节点的情况下确定其父节点内的子节点的索引?
E.g.,
例如,
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;
}
}
Is there a better way to determine the child's index?
有没有更好的方法来确定孩子的指数?
回答by KhalilRavanna
I've become fond of using indexOf
for this. Because indexOf
is on Array.prototype
and parent.children
is a NodeList
, you have to use call();
It's kind of ugly but it's a one liner and uses functions that any javascript dev should be familiar with anyhow.
我已经喜欢用indexOf
这个了。因为indexOf
is onArray.prototype
和parent.children
is a 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);
回答by Liv
you can use the previousSibling
property to iterate back through the siblings until you get back null
and count how many siblings you've encountered:
您可以使用该previousSibling
属性遍历兄弟姐妹,直到返回null
并计算遇到的兄弟姐妹数量:
var i = 0;
while( (child = child.previousSibling) != null )
i++;
//at the end i will contain the index.
Please note that in languages like Java, there is a getPreviousSibling()
function, however in JS this has become a property -- previousSibling
.
请注意,在 Java 等语言中,有一个getPreviousSibling()
函数,但在 JS 中,这已成为一个属性 -- previousSibling
。
回答by Abdennour TOUMI
ES6:
ES6:
Array.from(element.parentNode.children).indexOf(element)
Explanation :
解释 :
element.parentNode.children
→ Returns the brothers ofelement
, including that element.Array.from
→ Casts the constructor ofchildren
to anArray
objectindexOf
→ You can applyindexOf
because you now have anArray
object.
element.parentNode.children
→ 返回 的兄弟element
,包括该元素。Array.from
→ 将 的构造函数children
转换为Array
对象indexOf
→ 您可以申请,indexOf
因为您现在有了Array
对象。
回答by philipp
ES—Shorter
ES——更短
[...element.parentNode.children].indexOf(element);
The spread Operator is a shortcut for that
扩展运算符是一种快捷方式
回答by mikemaccana
Adding a (prefixed for safety) element.getParentIndex():
添加一个(安全前缀)element.getParentIndex():
Element.prototype.PREFIXgetParentIndex = function() {
return Array.prototype.indexOf.call(this.parentNode.children, this);
}
回答by Hyman Giffin
I hypothesise that given an element where all of its children are ordered on the document sequentially, the fastest way should be to to do a binary search, comparing the document positions of the elements. However, as introduced in the conclusion the hypothesis is rejected. The more elements you have, the greater the potential for performance. For example, if you had 256 elements, then (optimally) you would only need to check just 16 of them! For 65536, only 256! The performance grows to the power of 2! See more numbers/statistics. Visit Wikipedia
我假设给定一个元素,它的所有子元素都在文档中按顺序排列,最快的方法应该是进行二分搜索,比较元素的文档位置。然而,正如结论中所介绍的,假设被拒绝。您拥有的元素越多,性能的潜力就越大。例如,如果您有 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);
Then, the way that you use it is by getting the 'parentIndex' property of any element. For example, check out the following demo.
然后,您使用它的方式是获取任何元素的 '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>
Limitations
限制
- This implementation of the solution will not work in IE8 and below.
- 该解决方案的这种实现不适用于 IE8 及以下版本。
Binary VS Linear Search On 200 thousand elements (might crash some mobile browsers, BEWARE!):
二进制 VS 线性搜索 20 万个元素(可能会导致某些移动浏览器崩溃,请注意!):
- In this test, we will see how long it takes for a linear search to find the middle element VS a binary search. Why the middle element? Because it is at the average location of all the other locations, so it best represents all of the possible locations.
- 在这个测试中,我们将看到线性搜索需要多长时间才能找到中间元素 VS 二进制搜索。为什么是中间元素?因为它处于所有其他位置的平均位置,所以它最好地代表了所有可能的位置。
Binary Search
二分查找
(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>
Backwards (`lastIndexOf`) Linear Search
向后(`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>
Forwards (`indexOf`) Linear Search
转发(`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
PreviousElementSibling 计数器搜索
Counts the number of PreviousElementSiblings to get the parentIndex.
计算 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>
No Search
没有搜索
For benchmarking what the result of the test would be if the browser optimized out the searching.
如果浏览器优化了搜索,测试结果会是怎样的基准测试。
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
震荡
However, after viewing the results in Chrome, the results are the opposite of what was expected. The dumber forwards linear search was a surprising 187 ms, 3850%, faster than the binary search. Evidently, Chrome somehow magically outsmarted the console.assert
and optimized it away, or (more optimistically) Chrome internally uses numerical indexing system for the DOM, and this internal indexing system is exposed through the optimizations applied to Array.prototype.indexOf
when used on a HTMLCollection
object.
但是,在 Chrome 中查看结果后,结果与预期相反。愚蠢的前向线性搜索惊人的 187 毫秒,3850%,比二分搜索快。显然,Chrome 以某种方式神奇地超越console.assert
并优化了它,或者(更乐观地)Chrome 在内部为 DOM 使用数字索引系统,并且这个内部索引系统通过在对象Array.prototype.indexOf
上使用时应用的优化来公开HTMLCollection
。
回答by cuixiping
Use binary search algorithmto improve the performace when the node has large quantity siblings.
当节点有大量兄弟时,使用二分搜索算法来提高性能。
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;
}
回答by 1.21 gigawatts
Could you do something like this:
你能不能做这样的事情:
var index = Array.prototype.slice.call(element.parentElement.children).indexOf(element);
https://developer.mozilla.org/en-US/docs/Web/API/Node/parentElement
https://developer.mozilla.org/en-US/docs/Web/API/Node/parentElement
回答by bortunac
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());
});
回答by ofir_aghai
<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>