動態 NodeList
這是文檔對象模型(DOM,Document Object Model)中的一個大坑. NodeList
對象(以及 HTML DOM 中的 HTMLCollection
對象)是一種特殊類型的對象. DOM Level 3 spec 規范 對 HTMLCollection
對象的描述如下:
DOM中的 NodeList
和 NamedNodeMap
對象是動態的(live); 也就是說,對底層文檔結構的修改會動態地反映到相關的集合NodeList
和 NamedNodeMap
中。 例如, 如果先獲取了某個元素(Element
)的子元素的動態集合 NodeList
對象, 然后又在其他地方順序添加更多子元素到這個DOM父元素中( 可以說添加, 修改, 刪除子元素等操作), 這些更改將自動反射到NodeList
, 不需要手動進行其他調用. 同樣地, 對DOM樹上某個Node
節點的修改,也會實時影響引用了該節點的 NodeList
和 NamedNodeMap
對象。
getElementsByTagName()
方法返回對應標簽名的元素的一個動態集合, 只要document發生變化,就會自動更新對應的元素。 因此, 下面的代碼實際上是一個死循環:
1 // XXX 實際中請注意... 2 // 適當的中間變量是一個好習慣 3 var divs = document.getElementsByTagName("div"); 4 var i=0; 5 6 while(i < divs.length){ 7 document.body.appendChild(document.createElement("div")); 8 i++; 9 }
死循環的原因是每次循環都會重新計算 divs.length
. 每次迭代都會添加一個新的 <div>
, 所以每次 i++
,對應的divs.length
也在增加, 所以 i
永遠比divs.length
小, 循環終止條件也就不會觸發[例外情況是dom中沒有div,不進入循環]。
你可能會覺得這種動態集合是個壞主意, 但通過動態集合可以保證某些使用非常普遍的對象在各種情況下都是同一個, 如document.images
, document.forms
, 以及其他類似的 pre-DOM集合。
靜態 NodeList
querySelectorAll()
方法的不同是它返回一個靜態的 NodeList
. 這是表示的 選擇器API規范 :
querySelectorAll()
方法返回的 NodeList
對象必須是靜態的, 而不能是動態的([DOM-LEVEL-3-CORE], section 1.1.1). 后續對底層document的更改不能影響到返回的這個 NodeList
對象. 這意味着返回的對象將包含在創建列表那一刻匹配的所有元素節點。
所以即便是讓 querySelectorAll()
和 getElementsByTagName()
具有相同的參數和行為, 他們也是有很大的不同點。 在前一種情況下, 返回的 NodeList
就是方法被調用時刻的文檔狀態的快照, 而后者總是會隨時根據document的狀態而更新。 下面的代碼就不會是死循環:
1 var divs = document.querySelectorAll("div"), 2 i=0; 3 4 while(i < divs.length){ 5 document.body.appendChild(document.createElement("div")); 6 i++; 7 }
在這種情況下沒有死循環, divs.length
的值永遠不會改變, 所以循環實際上就是將 <div>
元素的數量增加一倍, 然后就退出循環。
為什么動態 NodeList 更快呢?
動態 NodeList
對象在瀏覽器中可以更快地被創建並返回,因為他們不需要預先獲取所有的信息, 而靜態 NodeList
從一開始就需要取得並封裝所有相關數據. 再三強調要徹底了解這一點, WebKit 的源碼中對每種 NodeList
類型都有一個單獨的源文件: DynamicNodeList.cpp 和 StaticNodeList.cpp. 兩種對象類型的創建方式是完全不同的。
DynamicNodeList
對象通過在cache緩存中 注冊它的存在 並創建。 從本質上講, 創建一個新的 DynamicNodeList
是非常輕量級的, 因為不需要做任何前期工作。 每次訪問 DynamicNodeList
時, 必須查詢 document 的變化, length 屬性 以及 item() 方法證明了這一點(使用中括號的方式訪問也是一樣的).
相比之下, StaticNodeList
對象實例由另一個文件創建,然后循環填充所有的數據 。 在 document 中執行靜態查詢的前期成本上比起 DynamicNodeList
要顯著提高很多倍。
如果真正的查看WebKit的源碼,你會發現他為 querySelectorAll()
明確地 創建一個返回對象 ,在其中又使用一個循環來獲取每一個結果,並創建最終返回的一個 NodeList
.
結論
getElementsByTagName()
速度比 querySelectorAll()
快的根本原因在於動態NodeList和靜態NodeList對象的不同。 盡管我可以肯定地說有某種方法來優化這一點, 在獲取NodeList
時不需要執行很多前期處理操作的動態列表,總比獲取靜態的集合(返回之前完成各種處理)要快很多。 哪個方法更好用主要還是看你的需求, 如果只是要根據 tag name 來查找元素, 也不需要獲取此一個快照, 那就應該使用 getElementsByTagName()
方法; 如果需要快照結果(靜態),或者需要使用復雜的CSS查詢, 則可以考慮 querySelectorAll()
。
*以上內容來源於網絡*