摘要:小结这下我们可以得出结论了个属性返回的对象不止能遍历到子元素,还能遍历到来自其原型的三个属性。既要防止那些添加修改了原型属性的对象遍历出多余的的结果,也要防止类似这种非标准属性返回一个属性的枚举性不可控的对象的坑。
问题的引出
关于DOM元素的children属性,以前我只在意它和childNodes属性的区别:即children属性只会返回子元素节点集合,而childNodes返回的就不止元素节点,还有文本节点等所有子节点集合。这样看来,children似乎是我们获取子元素而舍弃其他类型的子节点的最佳选择,虽然说在IE8-的浏览器下用它还会返回注释节点,但兼容起来也是很简单的。
我们知道,children返回的子元素集合实际上是一个类似数组的HTMLCollection对象,那接下来我们要获取每个子元素自然要遍历它咯,但是一遍历,问题就出来了:
上面的代码使用了for-in进行遍历,但我们预料中的结果并未出现,以chrome为例,运行结果是这个:
这就是我在前面的原生js练习题-第一课那篇文章中提到过的坑了,其中有两个奇怪的问题:
多返回了length等几个在数组应该是不可枚举的属性。
把有id的元素重复了两次。
关于问题1先说一下,上面提到的第一点在各个浏览器里情况都相同,而第二点关于返回的元素是否重复在各浏览器下情况还不同。
我们先讨论第一点,这里要考虑for-in循环遍历对象时的规则比较奇葩:对象自身和继承到的可枚举属性都会被遍历到。所以为确定多遍历到的内容到底是自身还是原型上的属性,我们来验证一下:
console.log(Object.keys(o)); //["0","1","2","i","ii"] console.log(Object.getOwnPropertyNames(o)); //["0","1","2","i","ii"]
Object.keys()方法返回的是可枚举的自身属性的属性名组成的数组,而Object.getOwnPropertyNames()返回的是所有自身属性的属性名组成的数组(含可枚举和不可枚举)。在这里我们没有看到length、item()、namedItem()三个属性的身影,由此断定他们不是HTMLCollection对象自身的属性,但既然能被for-in遍历到那就只能是来自HTMLCollection原型的可枚举属性。我们可以用Object.getOwnPropertyDescriptor()来验证其在原型上的可枚举性:
console.log(Object.getOwnPropertyDescriptor(o.__proto__, "length").enumerable); //true console.log(Object.getOwnPropertyDescriptor(o.__proto__, "item").enumerable); //true console.log(Object.getOwnPropertyDescriptor(o.__proto__, "namedItem").enumerable); //true关于问题2
解决了多出来的三个属性的来源,我们再回过头看看为什么会把有id的元素重复了两次。观察用Object.keys()方法返回的数组,这两次一次用下标做属性名、一次用id名作属性名。但其实两个属性名指向的是同一个对象:
o[0]===o["i"] //true o[1]===o["ii"] //true
可见之所以for-in会把id的元素重复遍历两次,不是因为有id的元素都添加进HTMLCollection对象两次,只是一个元素有了两个属性名而已,这是chrome的情况(我的版本是48.0.2564.116 m),但放到火狐和IE下结果却还有点所不同:
//FF console.log(Object.keys(o)); //["0", "1", "2"] console.log(Object.getOwnPropertyNames(o)); // ["0", "1", "2", "i", "ii"] o[0]===o["i"] //true o[1]===o["ii"] //true //IE11 console.log(Object.keys(o)); //["i", "ii", "2"] console.log(Object.getOwnPropertyNames(o)); // ["i", "ii", "2"] o[0]===o["i"] //true o[1]===o["ii"] //true
可见虽然不同的浏览器返回的HTMLCollection对象都存在有id的子元素有两个属性名的情况,但从Object.keys(o)的结果看,火狐和IE对同一元素默认只取一个属性名。所以如果你在火狐或IE运行一开始那段代码,就会发现for-in遍历时火狐和IE也只对同一元素访问一次,不会像chrome那样重复遍历。然而我们还看到,这两个浏览器间选取的属性名也不同,火狐优先选择下标形式,而IE优先选择id形式,同时由Object.getOwnPropertyNames(o)的结果我们还可以窥探出火狐实现选取属性名的机制可能是通过将id形式的属性名设为不可枚举来实现的,至于IE就不清楚了。
小结这下我们可以得出结论了:children个属性返回的HTMLCollection对象不止能遍历到子元素,还能遍历到来自其原型的length、item()、namedItem()三个属性。而且一旦遍历到的子元素有id,就存在HTMLCollection对象里一个元素会有两个属性名的问题,更让人蛋疼的是各浏览器对这两个属性名的选取各不相同。当然,最根本原因还是因为children属性现在还没被正式纳入标准,在使用这种非标准属性时我们难免遇到一些奇葩的状况。
所以这也告诫我们,如果对一个非标准属性的特点不是特别了解,还是不要轻易使用它,否则出现的问题往往是你难以控制的。但如果你还是觉得children使用起来方便,那在使用时就得谨记这些问题,比如在遍历子元素时最好放弃for-in循环,老老实实使用基本的for循环去遍历数字索引吧,这样就和遍历数组差不多,不会遍历到那些多出来的属性了。
而至于for-in,最好只用来遍历数组或简单的对象。既要防止那些添加、修改了原型属性的对象遍历出多余的的结果,也要防止类似children这种非标准属性返回一个属性的枚举性不可控的对象的坑。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/87704.html
摘要:直到内部的全部循环结束为止,才进入下一个元素,当循环结束时,内部的节点都已经生成好了。 自己实现虚拟 DOM 从 HTML 中提炼数据结构 先来看下我们的 HTML 傅雷家书 读家书,想付雷 从 HTML 中我们可以抽离出它的数据结构: 首先页面中只需要一个根节点root,定义为:nodesDate数组 root内有两个子元素h1和span,数组有两项,每项为内...
摘要:一个表达式是由一个或多个被分割的定位步组成。对于此类断言,我们可以使用谓词根据额外的遍历树来过滤出符合条件的节点。所以用来做一些低水平或与应用无关的事情遍历树来找指定属性的节点让人蛋疼。这是一个专门用来让你使用简洁的惯用表达式来遍历的工具。 编者注: XPath 即为XML路径语言(XML Path Language),它是一种用来确定XML文档中某部分位置的语言。 XPat...
摘要:一个表达式是由一个或多个被分割的定位步组成。对于此类断言,我们可以使用谓词根据额外的遍历树来过滤出符合条件的节点。所以用来做一些低水平或与应用无关的事情遍历树来找指定属性的节点让人蛋疼。这是一个专门用来让你使用简洁的惯用表达式来遍历的工具。 编者注: XPath 即为XML路径语言(XML Path Language),它是一种用来确定XML文档中某部分位置的语言。 XPat...
摘要:展开的属性后发现,继承于一个对象,而这个对象又继承于对象。这证实了我们对的猜想。是比较新的模型,相比更加完善,不光有元素,还有节点和。关于,和的关系,就是长得像,有个别相似的功能,但是是完全不一样的东西。 Array,NodeList, HTMLCollection这三个概念和它们之间的关系有很多做了几年前端的同学都搞不清楚,经常遇到但是又感觉很陌生,剪不断理还乱的感觉。今天咱们就来理...
阅读 1668·2021-11-19 09:40
阅读 2923·2021-09-24 10:27
阅读 3214·2021-09-02 15:15
阅读 1875·2019-08-30 15:54
阅读 1201·2019-08-30 15:54
阅读 1368·2019-08-30 13:12
阅读 624·2019-08-28 18:05
阅读 2792·2019-08-27 10:53