HTMLCollection與NodeList

specialCoder發表於2019-02-16

NodeList v.s. HTMLCollection

主要有兩個方面不一樣
1.包含節點的型別
2.使用方法

1.包含節點的型別不同(重要)

(1)NodeList
一個節點的集合,既可以包含元素和其他節點(註釋節點、文字節點等)。
(2)HTMLCollection
元素集合, 只有Element

2.使用方法

相同點:
1)    它們都有length屬性
2)    都有元素的getter,叫做item,可以傳入索引值取得元素。
3)    都是類陣列
不同點:
HTMLCollection還有一個nameItem()方法,可以返回集合中name屬性和id屬性值的元素。(部分瀏覽器也支援NodeList的nameItem()方法)

詳細講解

涉及獲取元素的主要API
DOM最初設計是為了解析XML而設計的,之後沿用到HTML上。我們可以把DOM分為兩部分 core 和 html,Core 部分提供最基礎的 XML 解析API說明,HTML 部分專為 HTML 中的 DOM 解析新增其特有的 API。NodeList介面是在core中體現的,HTMLCollection則是在html部分,不同瀏覽器也會實現它們的不同介面。但是現在的dom標準已經不分core和html了,反映的是瀏覽器的實現()。唯一要注意的是 querySelectorAll 返回的雖然是 NodeList ,但是實際上是元素集合,並且是靜態的(其他介面返回的HTMLCollection和NodeList都是live的)。
DOM中的NodeList NamedNodeMap 及 HTMLCollection
把這三個放在一起說,是因為三者都是DOM中的array-like物件,即類陣列物件(因而也都具有length屬性)。
(1)先說NamedNodeMap這個物件,這個比較簡單,雖然翻譯過來是 命名的節點對映,但它只不過是 Attr這個物件的一個集合,Attr物件是DOM元素節點的屬性的物件表達。通過元素節點(element node)的attributes屬性返回的就是NamedNodeMap這個物件。與NodeList相同的是它也是一個動態的集合(live collection),與NodeList不同的是,NamedNodeMap中儲存的是一組無序的屬性節點的集合。
(2)NodeList物件是由childNodes屬性,querySelectorAll方法返回的一組節點的集合,它儲存著一組有序的節點。注意區別的是,由childNodes屬性返回的NodeList物件是一個動態的集合(live collection), 而由querySelectorAll方法返回的則是一個靜態的集合(static collection)。因而在MDN中將他定義為 ”A sometimes-live collection“,live collection 指的是對對DOM的操作引起的的變化會實時的反映在這個集合裡。
(3)接下來就是HTMLCollection,它在本質是一個動態的NodeList物件。getElementsByTagName等方法返回的是包含零或多個元素的NodeList,在HTML文件中,返回的則是HTMLCollection物件。因此說它在本質上一個NodeList物件,包含一組有序(in document order基於文件結構順序)的動態集合。

在獲取原生DOM元素的時候,主要涉及這幾個DOM API(連結為Living Standard):

•    Node及對應集合NodeList
•    Element(繼承Node)及對應集合HTMLCollection
•    Document(繼承Node)

注:計劃取代NodeList和HTMLCollection的Elements目前並無廣泛實現

基礎知識 — NodeList v.s. HTMLCollection
使用Node Interface的方法,如childNodes,得到的通常是NodeList,而使用其他Interface的方法,又有可能得到HTMLCollection。所以有必要了解一下這兩者的區別。
關於這兩個型別的差異,在Stackoverflow上有一個不錯的問答。
其實,只要先看看Living Standard中這兩個型別的IDL,便能猜出大概了。NodeList的IDL如下:

interface NodeList {
  getter Node? item(unsigned long index);
  readonly attribute unsigned long length;
  iterable<Node>;
};

而HTMLCollection的IDL如下:

interface HTMLCollection {
  readonly attribute unsigned long length;
  getter Element? item(unsigned long index);
  getter Element? namedItem(DOMString name);
};

相同點:
4) 都是類陣列物件
5) 它們都有length屬性
6)都有元素的getter,叫做item
不同點:
1.NodeList的元素是Node,HTMLCollection的元素是Element。

Element繼承自Node,是Node的一種,在HTML中,它一般是HTML元素(比如<p>之類的標籤建立出來的物件)。而Node作為父類,除了Element還有一些其他子類,比如HTML元素內的文字對應的Text,文件對應的Document,註釋對應的Comment。HTMLCollection裡,只有Element,而NodeList裡可以有Element、Text、Comment等多種元素。按說如果獲取元素返回的列表裡只有Element,那這兩種類沒多大區別,但事實上很多時候瀏覽器會將解析HTML文字時得到的Text和Comment一併放進列表裡放回。比如說下面這一段程式碼

<div>
    <!-- Comment -->
    <p>This is Some Text</p>
</div>

若將這個div的子元素放在列表裡返回,那麼如果是作為NodeList返回,瀏覽器最多可以給這個列表5個元素(不同瀏覽器可能不同)

1.    一個<div>和註釋間的斷行和空格(或tab)作為text node(沒錯,標籤之間的空白符號也可以被解析為text node)
2.    註釋作為comment node
3.    註釋和<p>之間的斷行和空格(或tab)作為text node
4.    p作為element
5.    </p>和</div>之間的斷行和空格(或tab)作為text node

因此NodeList裡可能會有很多一般DOM操作不需要的text node和comment node需要處理。而HTMLCollection則簡單多了,只有<p>這一個元素,這也是比較符合大多數人直覺的結果。
2.HTMLCollection還有一個namedItem方法,可以快速獲取其中元素。假設有這樣一段HTML:

<div>
 <!-- Comment -->
<p>This is Some Text</p>
<img name="test" src="test.jpg">
</div>

那麼假設得到了這個div的子元素構成的HTMLCollection,叫做list,那麼使用list.namedItem(“test”)就可以直接得到裡面的img元素。
查詢順序參考Living Standard,但是在現實中不是所有瀏覽器都遵循標準。比如標準規定如果有多個擁有相同id或者name的元素,只要返回第一個,但chrome和opera會將它們放在一個HTMLCollection或者NodeList裡一併返回,參見MDN。
從IDL看不出來的還有如下幾點

1.    這兩個類都是“live”的。對其中元素進行操作,會實時反映到DOM中(也因此如果一次性直接在這類列表上進行多個DOM操作的話,帶來的開銷會很大)。
2.    item和namedItem都可以通過[]的縮寫進行呼叫,有的瀏覽器還支援用()的縮寫進行呼叫(也就是可以list[index],list[key]或者list(index),list(key)),以及直接用dot notation呼叫namedItem(比如list.key)。
3.    部分瀏覽器支援對NodeList呼叫namedItem或間接通過[]、()、dot notation來呼叫namedItem,但由於各瀏覽器支援不同,最好不對NodeList做這種操作。
4.    IE8及以下版本瀏覽器中,註釋屬於HTMLCommentElement,算作Element,因此會出現在HTMLCollection裡。

NodeList
  NodeList是一個節點的集合(既可以包含元素和其他節點),在DOM中,節點的型別總共有12種,通過判斷節點的nodeType來判斷節點的型別。
  我們可以通過Node.childNodes和document.querySelectAll() (返回NodeList的介面有很多,這裡不一一列舉,下同)來獲取到一個NodeList物件。
  NodeList物件有個length屬性和item()方法,length表示所獲得的NodeList物件的節點個數,這裡還是要強調的是節點,而item()可以傳入一個索引來訪問Nodelist中相應索引的元素。

1 <body>
 2     <div id="node">
 3         文字節點
 4         <!-- 註釋節點 -->
 5         <span>node1</span>
 6         <span>node2</span>
 7         <span>node3</span>
 8     </div>
 9 </body>
10 <script>
11     var node = document.getElementById(`node`),
12         nodeLists = node.childNodes
13     console.log(nodeLists.length) //     輸出為9
14 </script>

  上面的HTML程式碼中,“文字節點”和父節點子節點的空格(連著的文字)算做一個文字節點,然後是一個註釋節點和註釋節點和元素節點之間的空格(換行會產生空格,空格算做文字節點)的文字節點,緊接著的是一個元素節點和元素節點之間的換行的文字節點,三個元素節點和元素節點間的兩個文字節點,最後是最後得元素節點和父元素之間的空格產生的文字節點,總共是9個節點。
  NodeList物件的一大特點是它返回的內容是動態的(live),也就是說我們上面程式碼獲取nodeLists是類似於“指標”的東西,所以在下面程式碼中我們在獲取了nodeLists之後再向node中插入一個建立的span標籤後,發現獲取到了nodeLists.length變為10了,但是querySelectorAll這個介面返回的nodeList物件比較特殊,它是個靜態(static)的物件。而且是元素的集合。

1 <body>
 2     <div id="node">
 3         文字節點
 4         <!-- 註釋節點 -->
 5         <span>node1</span>
 6         <span>node2</span>
 7         <span>node3</span>
 8     </div>
 9 </body>
10 <script>
11     var node = document.getElementById(`node`)
12     var nodeLists = node.childNodes
13     var queryNodes = node.querySelectorAll(`span`)
14     node.appendChild(document.createElement(`span`))
15     console.log(nodeLists.length)  // 輸出為10
16     console.log(queryNodes.length)  //輸出為3
17 </script>

  HTMLCollection
  HTMLCollection是元素集合,它和NodeList很像,有length屬性來表示HTMLCollection物件的長度,也可以通過elements.item()傳入元素索引來訪問。當時它還有一個nameItem()方法,可以返回集合中name屬性和id屬性值得元素。HTMLDocument 介面的許多屬性都是 HTMLCollection 物件,它提供了訪問諸如表單、影像和連結等文件元素的便捷方式,比如document.images和document.forms的屬性都是HTMLCollection物件。

 1 <body>
 2     <img src="test.png" id="image1">
 3     <img src="test.png" id="image2">
 4     <img src="test.png" id="image3">
 5     <img src="test.png" id="image4">
 6     <img src="test.png" id="image5">
 7     <img src="test.png" id="image6">
 8 </body>
 9 <script>
10     console.log(document.images.namedItem(`image1`)) //<img src="test.png" id="image1">
11 </script>

  HTMLCollection的集合和NodeList物件一樣也是動態的,他們獲取的都是節點或元素集合的一個引用。
  HTMLCollection和NodeList的實時性非常有用,但是,我們有時要迭代一個NodeList或HTMLCollection物件的時候,我們通常會選擇生成當前物件的一個快照或靜態副本:
轉換為陣列型別:
var staticLists = Array.prototype.slice.call(nodeListorHtmlCollection, 0)
 這樣的話,我們就可以放心的對當前的DOM集合做一些刪減和插入操作,這個在DOM密集操作的時候很有用。

相關文章