關於DOM元素的children
屬性,之前我只在乎它和childNodes
屬性的區別:即children
屬性只會返回子元素節點集合,而childNodes
返回的就不止元素節點,還有文本節點等全部子節點集合。這樣看來,children
彷佛是咱們獲取子元素而捨棄其餘類型的子節點的最佳選擇,雖說在IE8-的瀏覽器下用它還會返回註釋節點,但兼容起來也是很簡單的。javascript
咱們知道,children
返回的子元素集合其實是一個相似數組的HTMLCollection對象,那接下來咱們要獲取每一個子元素天然要遍歷它咯,可是一遍歷,問題就出來了:html
<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爲例,運行結果是這個:java
這就是我在前面的原生js練習題-第一課那篇文章中提到過的坑了,其中有兩個奇怪的問題:chrome
多返回了length
等幾個在數組應該是不可枚舉的屬性。數組
把有id的元素重複了兩次。瀏覽器
先說一下,上面提到的第一點在各個瀏覽器裏狀況都相同,而第二點關於返回的元素是否重複在各瀏覽器下狀況還不一樣。spa
咱們先討論第一點,這裏要考慮for-in
循環遍歷對象時的規則比較奇葩:對象自身和繼承到的可枚舉屬性都會被遍歷到。因此爲肯定多遍歷到的內容究竟是自身仍是原型上的屬性,咱們來驗證一下:code
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()
來驗證其在原型上的可枚舉性:htm
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
解決了多出來的三個屬性的來源,咱們再回過頭看看爲何會把有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
這種非標準屬性返回一個屬性的枚舉性不可控的對象的坑。