HTMLCollection vs. NodeList

weixin_34195546發表於2017-01-18

本文章著作權歸飢人谷_Lyndon和飢人谷所有,轉載請註明出處


>>> 引文

這篇部落格起源於我對一道作業題的思考,在DOM課程中,第二道作業題是:

elem.childrenelem.childNodes的區別?

那麼這兩者的區別究竟是什麼呢?當時我在回答的時候寫了這樣一段程式碼(這段程式碼只得到表面上的答案,是淺層次理解)。

<div id="ct">
    <p class="para">Lyndon</p>
    <p class="attr">123<span>dozz</span></p>
</div>
<script>
    var ct = document.getElementById("ct");
    console.log(ct.children);
    console.log(ct.childNodes);
</script>

返回的結果是:

3691668-8f98162685028332.png

可以看出,當我用getElementById方法匹配到id = "ct"的元素節點後

  • ct.children返回的是一個HTMLCollection(圖中已用紅框標出),其中包含的兩個元素是p.para以及p.attr
  • ct.childNodes返回的是一個NodeList(圖中已用紅框標出),其中包含的元素稍微多些,有5項:text, p.para, text, p.attr, text

每一個元素不斷展開,會發現有很多的屬性,零零碎碎的,這時候我發現一個比較明顯的區別是textContent的不同:

  • HTMLCollection
    • p.paratextContent"Lyndon"
    • p.attrtextContent"123dozz"
  • NodeList
    • texttextContent"↵ "
    • p.paratextContent"Lyndon"
    • texttextContent"↵ "
    • p.attrtextContent"123dozz"
    • texttextContent"↵ "

究竟為何兩個方法會返回不一樣的結果?老師的課件歸納如下:

兩者的不同點在於:

  1. HTMLCollection物件具有namedItem()方法,可以傳遞id或name獲得元素;
  2. HTMLCollection的item()方法和通過屬性獲取元素(document.forms.f1)可以支援id和name,而NodeList物件只支援id

但是我並沒有完全看懂,大概掌握程度是0.6左右,於是我覺得解決這些疑惑的終極方法,應該是去深入瞭解:HTMLCollectionNodeList本質上的不同


>>> 首先,參考stack overflow上的回答

我翻譯了一下Vote數最高的答案:

HTMLCollectionNodeList都是DOM節點的集合,兩者都屬於Collections範疇,兩者的區別在於:

  • 方法略有差異HTMLCollectionNodeList多了一個namedItem方法,其他方法保持一致
  • 包含節點型別不同NodeList可以包含任何節點型別,HTMLCollection只包含元素節點(ElementNode)

Collections的出現場景?

  • 當返回多個節點(如:getElementByTagName)或者得到所有子元素(如:element.childNodes)時,Collections就會出現,這時候就有可能返回HTMLCollection或者NodeList

>>> 其次參考W3的文件(MDN上也有詳細解釋):

HTMLCollection是以節點為元素的列表,可以憑藉索引、節點名稱、節點屬性來對獨立的節點進行訪問。HTML DOM中的Collections是實時變動的,當原始檔案變化,Collections也會隨之發生變化。

  • 屬性:length(返回的是列表的長度)
  • 方法1:item(通過序號索引來獲取節點,引數是索引值,超過索引值返回null)
<div id="ct">
    <p class="para">Lyndon</p>
    <p class="attr">123<span>dozz</span></p>
    <form action="" method="get" name="apply">
        <input type="text" name="username" placeholder="使用者名稱">
        <input type="password" name="password" placeholder="密碼">
    </form>
</div>
<script>
    var ct = document.getElementById("ct");
    var a = ct.children;
    var b = ct.childNodes;
    console.log(a);
    console.log(b);
</script>
3691668-6b8941d8bca4248a.png
  • 方法2:namedItem(用名字來返回一個節點,首先搜尋是否有匹配的id屬性,如果沒有就尋找是否有匹配的name屬性,如果都沒有,返回null)
<div id="ct">
    <p class="para">Lyndon</p>
    <p class="attr">123<span>dozz</span></p>
    <form action="" method="get" name="apply">
        <input type="text" name="username" placeholder="使用者名稱">
        <input type="password" name="password" placeholder="密碼">
    </form>
</div>
<script>
    var ct = document.getElementById("ct");
    var a = ct.children;
    var b = ct.childNodes;
    console.log(a);
    console.log(b);
</script>
3691668-6b5c49422e362331.png

NodeList返回節點的有序集合,DOM中的NodeList也是實時變動的

  • 屬性:length(列表中節點的數量)
  • 方法:item(返回集合中的元素,如果超過範圍返回null)
<div id="ct">
    <p class="para">Lyndon</p>
    <p class="attr">123<span>dozz</span></p>
    <form action="" method="get" name="apply">
        <input type="text" name="username" placeholder="使用者名稱">
        <input type="password" name="password" placeholder="密碼">
    </form>
</div>
<script>
    var ct = document.getElementById("ct");
    var a = ct.children;
    var b = ct.childNodes;
    console.log(a);
    console.log(b);
</script>
3691668-a30ea9e9bd0f46ee.png

>>> Element與Node

到這一步,兩者本質上的區別已經差不多分清楚了,那麼現在就要進入第二個問題,為什麼兩個Element屬性返回的結果(如:textContent)不一樣呢?這裡需要感謝同班同學 joyside,他推薦給我一篇文章《Element和Node的區別你造嗎?》來理解Element和Node的區別。
文章中參考的是MDN:Node是一個基礎型別,document, element, text, comment, DocumentFragment等都繼承於Node. 在這篇文章最開始的測試中NodeList結果中有非常多的text,其實element, text, comment都是Node的子類,可以將它們視為:elementNode, textNode以及commentNode.平時在DOM中最常用的Element物件,其本質就是elementNode.
由於Node就是DOM的結構,程式碼內容經過解析後,Node與Node之間可以插入文字,文章最開頭的截圖中的"↵ "本質上就是Node之間的空隙,這種空隙的本質是textNode.


>>> 總結

綜上所述,進行歸納,並回答文章開頭提出的疑問。

  • HTMLCollectionNodeList的共同點顯而易見:

    1. 都是類陣列物件,都有length屬性
    2. 都有共同的方法:item,可以通過item(index)或者item(id)來訪問返回結果中的元素
    3. 都是實時變動的(live),document上的更改會反映到相關物件上(例外:document.querySelectorAll返回的NodeList不是實時的)
  • HTMLCollectionNodeList的區別是:

    1. NodeList可以包含任何節點型別,HTMLCollection只包含元素節點(elementNode),elementNode就是HTML中的標籤
    2. HTMLCollectionNodeList多一項方法:namedItem,可以通過傳遞id或name屬性來獲取節點資訊
  • 文章開頭的疑問解答:
    文章開頭的程式碼實際上等價於:

這裡是介於node與node之間的textNode
<div id="ct">
    這裡是介於node與node之間的textNode
    <p class="para">Lyndon</p>
    這裡是介於node與node之間的textNode
    <p class="attr">123<span>dozz</span></p>
這裡是介於node與node之間的textNode
</div>
<script>
    var ct = document.getElementById("ct");
    console.log(ct.children);
    console.log(ct.childNodes);
</script>

由於NodeList包含任何節點型別,ct.childNodes會一併返回textNode, elementNode等,所以最終結果就是由text, p, text, p, text組成的類陣列物件,這裡的text只是換行符而已。
由於HTMLCollection僅包含elementNode,因此最終的結果就是由p.para, p.attr組成的類陣列物件。當然,由於這裡只返回直接的子元素,因此不會出現類陣列物件中沒有span,如果希望返回結果中有span,這樣寫就可以了:

<div id="ct">
    <p class="para">Lyndon</p>
    <p class="attr">
        <span>dozz</span>
    </p>
    <span>bilibili</span>
</div>
<script>
    var ct = document.getElementById("ct");
    console.log(ct.children);
    console.log(ct.childNodes);
3691668-17fef0cc12d09887.png

老師在課件中還詳細給出了有哪些具體的方法可以獲取HTMLCollectionNodeList物件,如果要記住可能比較麻煩,每次在具體情況時參考控制檯的輸出,得知型別後只需要記住常用的方法區別就可以輕鬆地進行操作了。


>>> 參考資料

  1. Difference between HTMLCollection, NodeLists, and arrays of objects
  2. Interface NodeList
  3. NodeList and HTMLCollection
  4. Interface HTMLCollection
  5. Element和Node的區別你造嗎?

相關文章