Skip to content

Latest commit

 

History

History
45 lines (26 loc) · 3.38 KB

20191104_hax_子元素数量和遍历子元素.md

File metadata and controls

45 lines (26 loc) · 3.38 KB

子元素数量和遍历子元素

如何得到一个元素的子元素数量?

有基本DOM API常识的前端程序员可能第一时间想到element.childNodes.length。不过childNodes既包含元素节点也包含文本节点,所以你还需要遍历过滤,不太符合要求。

如果我们仔细查看DOM spec,会发现两个符合要求的 API:

  • element.children.length
  • element.childElementCount

那么问题来了,为什么同一件事情需要两个API呢?

children返回所有子元素,看上去是比较有用的,那childElementCount是不是多余的呢?而且如果仔细看文档,跟childElementCount一起的Element Traversal API还有一堆其他接口,包括firstElementChild、lastElementChild、previousElementSibling、nextElementSibling。这些接口是否多余的呢?在实践中和children相比,孰优孰劣?

这是一个好问题。

我们分几点来说。

首先我们可以发现 previousElementSibling, nextElementSibling 并不是多余的,因为 children 接口做不到同样的事情。

其次 firstElementChild, lastElementChild 虽然等价于 children[0] 和 children[children.length - 1] ,但是前者避免了先生成一个 children 对象,所以是更直接且性能更好的接口(尤其是对 lastElementChild 来说)。

注意 children 是一个 live collection,按照通常的 DOM 实现来说,使用 firstElementChild 配合 nextElementSibling 的正向遍历(或者 lastElementChild 配合 previousElementSibling 的反向遍历)可以比基于children索引的遍历获得更好的性能。

for (let i = 0; i < e.children.length; ++i) {
	process(e.children[i])
}

// better
for (let child = e.firstElementChild; child != null; child = child.nextElementSibling) {
	process(child)
}

最后,childElementCount 确实是多余的接口。【小彩蛋:如果你打开 spec 的源码会发现有一句注释:】

Element Traversal API 是在2007年左右开始制定的,本源来自于 SVG Micro DOM 的需求(而不像其他 DOM 新接口主要来自于浏览器厂商联盟 WHATWG),原本是没有 childElementCount 属性的。当时从 SVG 工作组接手的 WebAPI 工作组加入了这个属性,但并没有将 children 属性(比较老的前端应该知道这原本是 IE 的私有接口)标准化列入议程。仅仅从 traversal 需求来说并不需要 children 属性,但某些次要需求其实需要 children count(参见ElementTraversal API 的代码示例),所以就加了这个属性。但 children 属性标准化之后,这个 childElementCount 就变得多余了。

其实在2011年左右,主导 HTML/DOM 标准制定的 WHATWG 有动议要删除这个属性,但是因为这个属性在之前的3年中已经在所有主流浏览器里实现了(2008年到2010年间,Chrome 1+、FF 3.5+、Safari 4+、IE9+ 都实现了此属性——是的,尽管很可能到今天还有许多前端并不知道这个接口的存在) ,所以到2012年年底的时候最后决定还是不删除了。

最后,现在的 DOM 标准应该直接看 WHATWG DOM Standard ,Element Traversal已经属于上一代标准了。