遍历DOM元素的children属性碰到的坑

题目的引出

关于DOM元素的children属性,之前我只在乎它和childNodes属性的区分:即children属性只会返回子元素节点鸠合,而childNodes返回的就不止元素节点,另有文本节点等一切子节点鸠合。如许看来,children似乎是我们猎取子元素而舍弃其他范例的子节点的最好挑选,虽然说在IE8-的浏览器下用它还会返回解释节点,但兼容起来也是很简朴的。

我们晓得,children返回的子元素鸠合实际上是一个相似数组的HTMLCollection对象,那接下来我们要猎取每个子元素天然要遍历它咯,然则一遍历,题目就出来了:

    <div id="ul">
        <div id='i'></div>
        <div id="ii"></div>
        <div></div>
    </div>

    <script>
        o=document.getElementById('ul').children;
        for (i in o) {
            console.log(o[i]);
        }
    </script>

上面的代码运用了for-in举行遍历,但我们预料中的效果并未涌现,以chrome为例,运转效果是这个:

《遍历DOM元素的children属性碰到的坑》

这就是我在前面的原生js练习题-第一课那篇文章中提到过的坑了,其中有两个新鲜的题目:

  1. 多返回了length等几个在数组应该是不可罗列的属性。

  2. 把有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()返回的是一切本身属性的属性名构成的数组(含可罗列和不可罗列)。在这里我们没有看到lengthitem()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对象不止能遍历到子元素,还能遍历到来自其原型的lengthitem()namedItem()三个属性。而且一旦遍历到的子元素有id,就存在HTMLCollection对象里一个元素会有两个属性名的题目,更让人蛋疼的是各浏览器对这两个属性名的拔取各不雷同。固然,最根本原因照样由于children属性如今还没被正式归入规范,在运用这类非规范属性时我们不免碰到一些奇葩的状态。

所以这也申饬我们,假如对一个非规范属性的特性不是迥殊相识,照样不要随意马虎运用它,不然涌现的题目往往是你难以掌握的。但假如你照样以为children运用起来轻易,那在运用时就得服膺这些题目,比如在遍历子元素时最好摒弃for-in轮回,老老实实运用基础的for轮回去遍历数字索引吧,如许就和遍历数组差不多,不会遍历到那些多出来的属性了。

而至于for-in,最好只用来遍历数组或简朴的对象。既要防备那些增加、修改了原型属性的对象遍历出过剩的的效果,也要防备相似children这类非规范属性返回一个属性的罗列性不可控的对象的坑。

    原文作者:Levon
    原文地址: https://segmentfault.com/a/1190000004499453
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞