為什麼 getElementsByTagName() 比 querySelectorAll() 快?
昨天,Yahoo 和 SoundManager 的創建者 Scott Schiller 在 Twitter 上表達了一些關於為什麼 getElementsByTagName("a")
的困惑 比 querySelectorAll("a")
快 在幾乎所有瀏覽器中。有一個比較兩者的 JSPerf 測試,您可以發現速度比較相當明顯。在我現在使用的瀏覽器中,Windows XP 上的 Firefox 3.6.8,querySelectorAll("a")
比 getElementsByTagName("a")
慢了令人震驚的 98% .我、Scott 和 YUI 團隊成員 Ryan Grove 在 Twitter 上討論了為什麼會這樣,以及這真的是多麼令人失望但並不出人意料。我想我會跟進一個更長的描述,說明為什麼會發生這種情況以及為什麼它可能不會有太大變化。
在深入細節之前,這兩種方法之間有一個非常重要的區別,並不是一種只接受標籤名稱而另一種接受完整的 CSS 選擇器。最大的區別在於返回值:getElementsByTagName()
方法返回一個實時 NodeList
而 querySelectorAll()
返回靜態 NodeList
.理解這一點非常重要。
實時節點列表
這是文檔對像模型的主要問題之一。 NodeList
對象(也就是 HTMLCollection
HTML DOM 中的對象)是一種特殊類型的對象。 DOM Level 3 規範說關於 HTMLCollection
對象:
getElementsByTagName()
方法返回這些實時元素集合之一,這些元素在文檔更改時自動更新。因此,以下實際上是一個無限循環:
var divs = document.getElementsByTagName("div"),
i=0;
while(i < divs.length){
document.body.appendChild(document.createElement("div"));
i++;
}
發生無限循環是因為 divs.length
每次通過循環重新計算。由於循環的每次迭代都在添加一個新的 <div>
,表示 divs.length
每次循環都會遞增,所以 i
,也是遞增的,永遠追不上,永遠不會觸發終端條件。
這些實時集合似乎是個壞主意,但它們的存在是為了使相同的對象能夠用於 document.images
, document.forms
,以及其他在瀏覽器中變得司空見慣的類似 pre-DOM 集合。
靜態節點列表
querySelectorAll()
方法不同,因為它是一個靜態的 NodeList
而不是一個活的。這在 Selectors API 規範中有說明:
所以即使 querySelectorAll()
的返回值 與 getElementsByTagName()
返回的方法和行為相同 ,它們實際上是非常不同的。在前一種情況下,NodeList
是調用方法時文檔狀態的快照,而後一種情況將始終與文檔的當前狀態保持同步。這*不是*無限循環:
var divs = document.querySelectorAll("div"),
i=0;
while(i < divs.length){
document.body.appendChild(document.createElement("div"));
i++;
}
在這種情況下沒有無限循環。 divs.length
的值 永遠不會改變,所以循環本質上會使 <div>
的數量增加一倍 文檔中的元素,然後退出。
那麼為什麼實時 NodeLists 更快?
直播NodeList
瀏覽器可以更快地創建和返回對象,因為它們不必在靜態 NodeList
時預先擁有所有信息 s 需要從一開始就擁有他們的所有數據。為了強調這一點,WebKit 源代碼對於每種類型的 NodeList
都有一個單獨的源文件 :DynamicNodeList.cpp 和 StaticNodeList.cpp。這兩種對像類型的創建方式非常不同。
DynamicNodeList
對像是通過在緩存中註冊其存在來創建的。本質上,聽到創建一個新的 DynamicNodeList
非常小,因為它不需要預先做任何工作。每當 DynamicNodeList
被訪問時,它必須查詢文檔的更改,如 length
所示 屬性和 item()
方法(與使用括號表示法相同)。
將此與 StaticNodeList
進行比較 對象,其實例在另一個文件中創建,然後用循環內的所有數據填充。與使用 DynamicNodeList
相比,在文檔上運行查詢的前期成本要高得多 實例。
如果您查看實際為 querySelectorAll()
創建返回值的 WebKit 源代碼 ,您會看到使用循環來獲取每個結果並構建 NodeList
最終返回。
結論
getElementsByTagName()
的真正原因 比 querySelectorAll()
快 是因為live和static的區別NodeList
對象。雖然我確信有辦法對此進行優化,但沒有為實時 NodeList
做前期工作 通常總是比創建靜態 NodeList
的所有工作要快 .確定使用哪種方法在很大程度上取決於您要執行的操作。如果您只是按標籤名稱搜索元素並且不需要快照,那麼 getElementsByTagName()
應該使用;如果您確實需要結果快照或者您正在執行更複雜的 CSS 查詢,那麼 querySelectorAll()
應該使用。