212

在直接的javascript中(即没有jQuery等扩展),是否有一种方法可以在不迭代和比较所有子节点的情况下确定父节点内部的子节点索引?

例如。,

var child=文档.getElementById('my_element');var parent=child.parentNode;var childNodes=parent.childNodes;var计数=childNodes.length;var子索引;for(var i=0;i<count;++i){if(child===childNodes[i]){child_index=i;断裂;}}

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

  • 2
    对不起,我是个十足的傻瓜吗?这里有很多看似已学会的答案,但要获得所有子节点,您不需要这样做parent.child节点,而不是父母.孩子?. 后者仅列出元素,尤其不包括文本节点。。。此处的一些答案,例如使用前兄弟姐妹,是基于使用所有子节点,而其他节点只关心以下子节点元素s…(!) 评论 2017年10月12日11:17
  • @mikerodent我不记得我最初问这个问题的目的是什么,但这是我不知道的一个关键细节。除非你小心,.child节点绝对应该使用,而不是.儿童。如你所指出的,前两个公布的答案将给出不同的结果。
    – 奥姆
    评论 2017年10月12日13:49
  • 1
    当计划在1000多个节点上执行数千次查找时,请将信息附加到该节点(例如通过child.dataset)。目标是将O(n)或O(n^2)算法转换为O(1)算法。缺点是,如果定期添加和删除节点,则附加到节点的相关位置信息也必须更新,这可能不会带来任何性能提升。偶尔的迭代不是什么大问题(例如单击处理程序),但重复的迭代是有问题的(例如mousemove)。 评论 2020年5月3日13:09

12个答案12

重置为默认值
206

我喜欢用索引属于为了这个。因为索引属于已打开阵列原型父母.孩子是一个节点列表,您必须使用调用();这有点难看,但它是一个一行程序,并且使用了任何javascript开发人员都应该熟悉的函数。

var child=文档.getElementById('my_element');var parent=child.parentNode;//parent.childrens.indexOf(child)的等效项var index=Array.prototype.indexOf.call(parent.children,child);
9
  • 9
    var index=[].indexOf.call(child.parentNode.children,child);
    – 崔西平
    评论 2014年8月15日17:04
  • 41
    Fwiw,使用[]每次运行该代码时都会创建一个Array实例,与使用阵列原型. 评论 2014年8月29日0:50
  • 20
    要评估[].索引引擎必须创建一个数组实例才能访问索引属于原型上的实现。实例本身未被使用(它执行GC,这不是泄漏,只是浪费了周期)。数组原型索引直接访问该实现,而不分配匿名实例。在几乎所有情况下,这种差异都可以忽略不计,所以坦率地说,这可能不值得关注。 评论 2015年6月24日17:03
  • 小心IE中的错误!Internet Explorer 6、7和8支持它,但错误地包含了Comment节点。来源“developer.mozilla.org/en-US/docs/Web/API/ParentNode/… 评论 2017年5月4日6:53
  • 2
    在现代javascript中,您可以使用[…parent.children].indexOf(child) 评论 2021年7月13日4:30
198

ES6:

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

说明:

  • 元素.父节点.子节点返回的兄弟要素,包括该元素。

  • 阵列起始位置铸造的构造函数儿童阵列对象

  • 索引属于你可以申请索引属于因为你现在有一个阵列对象。

  • 1
    Internet Explorer还活着吗?只是乔克。。好的,所以你需要一个聚乙烯填料制造阵列起始位置在Internet explorer上工作 评论 2017年3月30日18:00
  • 18
    根据MDN,呼叫数组.from() 从类数组或可迭代对象创建新的数组实例。仅仅为了查找索引而创建一个新的数组实例可能会降低内存或GC的效率,这取决于操作的频率,在这种情况下,如公认的答案中所解释的那样,迭代会更理想。
    – 大块
    评论 2017年6月28日7:30
  • 2
    @TheDarkIn1978我知道在代码优雅和应用程序性能之间存在权衡👍🏻 评论 2017年8月26日20:04
159

您可以使用上一个同级属性循环遍历兄弟级,直到您回来无效的计算你遇到过多少兄弟姐妹:

var i=0;while((child=child.previousSibling)!=null)i++;//最后,我将包含索引。

请注意,在Java等语言中,有一个获取上一个Sibling()函数,但在JS中,这已成为一个属性--上一个同级.

使用上一个元素同级next元素同级忽略文本和注释节点。

15
  • 是的。不过,您在文本中留下了getPreviousSibling()。 评论 2011年5月6日16:06
  • 7
    这种方法需要相同的迭代次数来确定子索引,所以我看不出它会有多快。
    – 迈克尔
    评论 2013年5月10日21:04
  • 40
    单行版本:for(var i=0;(node=node.previousSibling);i++); 评论 2014年8月29日0:53
  • 2
    @sfarbota Javascript不知道块作用域,所以将是可访问的。 评论 2014年9月22日15:12
  • 9
    @nepdev那是因为.上一个同级.previous元素同级前者命中文本节点,后者则不命中。 评论 2016年5月23日17:39
66

ES较短

[…element.parentNode.childrens].indexOf(element);

排列运算符是实现该操作的快捷方式

4
  • 1
    两者的区别是什么e.parentElement.childNodes(e.parentElement.childNodes)e.parentNode.children类?
    – 网状物
    评论 2018年3月8日13:44
  • 11
    子节点还包括文本节点
    – 菲利普
    评论 2018年3月8日13:57
  • 1
    使用您得到的Typescript类型“NodeListOf<ChildNode>”必须具有返回迭代器.ts(2488)的“[Symbol.iterator]()”方法 评论 2019年3月18日6:16
  • 使用时打字稿,您需要更新您的tsconfig.json(配置.json)导入正确的接口。“lib”:[“dom”,“dom.iterable”]。确保包括域可更改. 评论 2022年8月25日13:15
13

𝗣𝗿𝗼𝗼𝗳 𝗢𝗳 𝗔 𝗟𝗲𝘀𝘀 𝗘𝗳𝗳𝗶𝗰𝗶𝗲𝗻𝘁 𝗕𝗶𝗻𝗮𝗿𝘆 𝗦𝗲𝗮𝗿𝗰𝗵

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

(函数(构造函数){'使用严格';Object.defineProperty(constructor.protype,'parentIndex'{获取:函数(){var searchParent=this.parentElement;if(!searchParent)返回-1;var searchArray=searchParent.children,thisOffset=this.offset顶部,stop=搜索数组长度,p=0,增量=0;while(searchArray[p]!==此){if(searchArray[p]>this)停止=p+1,p-=增量;δ=(停止-p)>>>1;p+=增量;}返回p;}});})(window.Element||节点);

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

(函数(构造函数){'使用严格';Object.defineProperty(constructor.protype,'parentIndex'{获取:函数(){var searchParent=this.parentNode;如果(searchParent===null)返回-1;var childElements=搜索Parent.children,lo=-1,mi,hi=childElements.length;while(1+lo!==hi){mi=(hi+lo)>>1;if(!(this.compareDocumentPosition(childElements[mi])&0x2){hi=英里;继续;}lo=mi;}return childElements[hi]===这个?hi:-1;}});})(window.Element||节点);output.textContent=document.body.parentIndex;output2.textContent=文档.documentElement.parentIndex;
Body parentIndex是<b id=“output”></b><br/>documentElements parentIndex是<b id=“output2”></b>

限制

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

200000个元素上的二进制与线性搜索(可能会导致某些移动浏览器崩溃,小心!):

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

二进制搜索

(函数(构造函数){'使用严格';Object.defineProperty(constructor.prototype,'parentIndexBinarySearch'{获取:函数(){var searchParent=this.parentNode;如果(searchParent===null)返回-1;var childElements=搜索Parent.children,lo=-1,mi,hi=childElements.length;while(1+lo!==hi){mi=(hi+lo)>>1;if(!(this.compareDocumentPosition(childElements[mi])&0x2){hi=英里;继续;}lo=mi;}return childElements[hi]===这个?hi:-1;}});})(window.Element||节点);test.inerHTML=“<div></div>”.重复(200e+3);//给它一些时间思考:requestAnimationFrame(函数(){var-child=test.childrens.item(99.9e+3);var start=performance.now(),end=Math.round(Math.random());for(var i=200+end;i--!==end;)控制台.assert(test.children.item(数学循环(99.9e+3+i+Math.random()).parentIndexBinarySearch);var end=性能.now();setTimeout(函数(){output.textContent='用二进制搜索'+((end-start)*10).toFixed(2)+'毫秒在一个包含20万个子元素的元素中找到第999千到101千个子元素。';test.remove();测试=空;//释放引用}, 125);}, 125);
<output id=output></output><div id=test style=可见性:隐藏;空白:预></div>

向后(`lastIndexOf`)线性搜索

(函数(t){“use strict”;vare=Array.prototype.lastIndexOf;Object.defineProperty(t.prototype,“parentIndexLinearSearch”,{get:function(){return e.call(t,this)}})})(window.Element | | Node);test.inerHTML=“<div></div>”.重复(200e+3);//给它一些时间思考:requestAnimationFrame(函数(){var-child=test.childrens.item(99e+3);var start=performance.now(),end=Math.round(Math.random());for(var i=2000+结束;i-!==结束;)控制台.assert(test.children.item(数学循环(99e+3+i+Math.random()).parentIndexLinearSearch);var end=性能.now();setTimeout(函数(){output.textContent='在一个包含20万个子元素的元素中,查找第999000到101000个子元素需要花费反向线性搜索'+(end-start).toFixed(2)+'毫秒。';test.remove();test=null;//释放引用}, 125);});
<output id=output></output><div id=test style=可见性:隐藏;空白:预></div>

转发(`indexOf`)线性搜索

(函数(t){“use strict”;var e=Array.prototype.indexOf;Object.defineProperty(t.prototype,“parentIndexLinearSearch”,{get:function(){return e.call(t,this)}})})(window.Element||Node);test.inerHTML=“<div></div>”.重复(200e+3);//给它一些时间思考:requestAnimationFrame(函数(){var-child=test.childrens.item(99e+3);var start=performance.now(),end=Math.round(Math.random());for(var i=2000+end;i--!==end;)控制台.assert(test.children.item(数学循环(99e+3+i+Math.random()).parentIndexLinearSearch);var end=性能.now();setTimeout(函数){output.textContent='向前线性搜索'+(end-start).toFixed(2)+'毫秒后,在一个包含20万个子元素的元素中找到了999000到101000个子元素。';test.remove();test=null;//释放引用}, 125);});
<output id=output></output><div id=test style=可见性:隐藏;空白:预></div>

上一个ElementSibling计数器搜索

统计PreviousElementSibling的数量以获取parentIndex。

(函数(构造函数){'使用严格';Object.defineProperty(constructor.prototype,'parentIndexSiblingSearch'{获取:函数(){var i=0,cur=this;做{cur=cur.previousElementSibling;++i;}while(cur!==空)返回i//返回3}});})(window.Element||节点);test.inerHTML=“<div></div>”.重复(200e+3);//给它一些时间思考:requestAnimationFrame(函数(){var-child=test.childrens.item(99.95e+3);var start=performance.now(),end=Math.round(Math.random());for(var i=100+end;i--!==end;)控制台.assert(test.children.item(数学循环(99.95e+3+i+Math.random()).parentIndexSiblingSearch);var end=性能.now();setTimeout(函数(){output.textContent='在一个包含20万个子元素的元素中,需要使用PreviousElementSibling搜索'+((end-start)*20).toFixed(2)+'毫秒才能找到999000到101000个子元素。';test.remove();test=null;//释放引用}, 125);});
<output id=output></output><div id=test style=可见性:隐藏;空白:预></div>

无搜索

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

test.inerHTML=“<div></div>”.重复(200e+3);//给它一些时间思考:requestAnimationFrame(函数(){var start=performance.now(),end=Math.round(Math.random());for(var i=2000+end;i--!==end;)console.assert(true);var end=性能.now();setTimeout(函数(){output.textContent='在一个包含20万个子元素的元素中,要找到第999千到101千个子元素,需要进行no搜索'+(end-start).toFixed(2)+'毫秒。';test.remove();test=null;//释放引用}, 125);});
<output id=output></output><br/><div id=test style=可见性:隐藏>

冲撞

然而,在Chrome中查看结果后,结果与预期相反。愚蠢的向前线性搜索令人惊讶地达到187毫秒,比二进制搜索快3850%。显然,Chrome以某种方式神奇地超过了控制台资产并对其进行了优化,或者(更乐观地说)Chrome内部对DOM使用了数字索引系统,该内部索引系统通过应用于数组原型索引在上使用时HTML集合对象。

  • 高效,但不切实际。 评论 2020年5月6日23:29
  • 谈论过早优化。对不起,这值得投反对票。。。为什么你要费心优化这么简单的查找,而这通常不是瓶颈的来源?如果你有成千上万的子节点,那么你可能做错了。 评论 2021年10月5日13:52
  • 我猜childNodes集合在引擎中是作为链接列表实现的,因此二进制搜索无法有效工作。这就解释了为什么上一个同级只是一件事父索引不是。 评论 2021年10月9日21:22
10

你能这样做吗:

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

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

9

制作getParentIndex(元素):

const getParentIndex=函数(元素){return Array.prototype.indexOf.call(element.parentNode.children,element);}

更新:我最初是在十年前用原型扩展写的,加上前缀以避免冲突,但后来我改变了使用带前缀原型的想法,并更新了这个答案,使其成为一个helper函数。

0
8

如果您的元素是<tr><td>,您可以使用行索引/单元格索引属性。

1
  • 注意,在<tr>元素,这将考虑表中可能包含的任何表标题。
    – 丹·O
    评论 2022年11月9日15:51
4

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

函数getChildrenIndex(ele){//IE使用Element.sourceIndexif(ele.sourceIndex){var eles=ele.parentNode.children;var低=0,高=eles.length-1,中=0;var esi=元素源索引,nsi;//使用二进制搜索算法while(低<=高){中等=(低+高)>>1;nsi=eles[mid].sourceIndex;如果(nsi>esi){高=中-1;}else if(nsi<esi){低=中等+1;}其他{返回中间;}}}//其他浏览器var i=0;while(ele=ele.previousElementSibling){i++;}返回i;}
1
  • 不起作用。我不得不指出,IE版本和“其他浏览器”版本会计算出不同的结果。“其他浏览器”技术按预期工作,获取父节点下的第n个位置,而IE技术“当对象出现在文档的所有集合中时,按源顺序检索对象的序号位置”(msdn.microsoft.com/en-gb/library/ie/ms534635(v=vs.85).aspx). 例如,我使用“IE”技术得到126,然后使用另一种技术得到4。 评论 2014年8月14日15:04

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

函数getChildNodeIndex(元素){   让位置=0;while((elem=elem.previousSibling)!=null){if(elem.nodeType!=节点.TEXT_Node)位置++;}返回位置;}
-1
Object.defineProperties(元素原型{组:{值:函数(str,context){var scope=上下文?上下文:this.parentNode;如果(!scope | | scope===文档)返回null;return[…scope.querySelectorAll(“:scope”+(context?“:”>“)+this.nodeName+(str||“”)];}},兄弟姐妹:{值:函数(str,context){var rez=this.group(str,context)| |[];rez.拼接(rez.indexOf(this),1);return rez;//!!!!!!!!!!!!!!!ATENTIE returneaza阵列(nu NodeList)}},第n个:{值:函数(str,context){return this.group(str,context).indexOf(this);}}}

前任

/*html格式*/<ul id=“the_ul”><li></li><li><li><li></li></ul>/*js型*/_ul.addEventListener(“点击”,ev=>{var tg=ev.目标;tg.setAttribute(“active”,true);tg.siblings().map(li=>{li.removeAttribute(“active”)});警报(“单击li”+tg.nth());});
5
  • 1
    你能解释一下为什么你从元素.原型? 这些函数看起来很有用,但我不知道这些函数做什么(即使命名很明显)。 评论 2015年12月4日19:04
  • @扩展元素原型的原因是相似性。。。4个前elemen.children、element.parentNode等。。。所以你用同样的方法处理元素。。。。group方法有点复杂,因为我想通过相同的nodeType和具有相同属性的元素(即使没有相同的祖先),将sibling方法稍微扩展到类似的元素 评论 2015年12月4日19:12
  • 我知道什么是原型扩展,但我想知道您的代码是如何使用的。el.group.value()(电子组值)??. 我的第一个评论是为了提高你答案的质量。
    – A1r泵
    评论 2015年12月4日19:36
  • group和兄弟方法返回Array,其中包含已建立的dom元素。。。。感谢您的评论以及发表评论的原因 评论 2015年12月4日19:44
  • 非常优雅,但也非常缓慢。 评论 2017年7月2日22:37
-2
<body><章节><section onclick=“childIndex(this)”>child a子级<section onclick=“childIndex(this)”>子b子c</节><脚本>函数childIndex(e){设i=0;while(e.parentNode.childrens[i]!=e)i++;警报(“儿童索引”+i);}</script></body>
2
  • 这里不需要jQuery。 评论 2019年10月15日16:23
  • @VitalyZdanevich是对的,但这也可能是真正使用的用户的解决方案。 评论 2020年8月24日23:41

您的答案

单击“发布您的答案”,表示您同意我们的服务条款并确认您已阅读我们的隐私政策.

不是你想要的答案吗?浏览标记的其他问题问你自己的问题.