相信很多人第一反應當然是這樣的了,querySelectorAll的參數是一個css selector,這一步還需要處理呢,肯定會比直接getElementsByTagName要慢了。具體jsperf上有相關的對比,確實getElementsByTagName要比querySelectorAll要快很多,這里不放具體鏈接了,因為這個網站掛了很長一段時間了已經。
廢話不說,直接看原文Why is getElementsByTagName() faster than querySelectorAll()?
這里大概翻譯一下,覺得不妥的地方還望指教。
首先這兩個方法有個非常明顯的區別,一個接受一個tag name的參數,另外一個接受一個css selector。這兩個方法最大的不同點是,getElementsByTagName返回的是一個動態的NodeList而querySelectorAll返回的是一個靜態的NodeList。
動態的NodeLists
NodeList和HTMLCollection是兩個特別的對象類型,3級DOM中是這樣定義HTMLCollection:
NodeList和NamedNodeMap在DOM中都是動態的,也就是說,改變這些dom結構都會影響到這些NodeList和NamedNodeMap。例如:如果獲取一個有子節點的NodeList,然后增加或者刪除子節點,這個NodeList也會動態的改變。同樣的,在一個tree結構中改變一個node會影響到所有引用這個node的NodeList和NamedNodeMap對象。
getElementsByTagName方法即返回的就是這樣的一個NodeList,看下面代碼
var divs = document.getElementsByTagName("div"),
i=0;
while(i < divs.length){
document.body.appendChild(document.createElement("div"));
i++;
}
這是一個死循環,因為divs.length在每次循環都會+1。
也許這樣的動態設計是一個缺陷,但是開發者已經習慣了這種動態的NodeList。
靜態的NodeLists
querySelectorAll返回的是一個靜態的NodeList,下面是 Selectors API
querySelectorAll返回的是一個靜態的NodeList。改變一個相關node不會改變這個靜態NodeList對象。也就是說這個對象必須在獲取的時候就被創建出來。
所以即使querySelectorAll返回的結果和getElementsByTagName是一樣的,但是他們還是有着本質的區別。在動態NodeList里return的是一個指針,而靜態的NodeList返回時當前的node所有信息。
所以下面這個循環就不是一個死循環
var divs = document.querySelectorAll("div"),
i=0;
while(i < divs.length){
document.body.appendChild(document.createElement("div"));
i++;
}
因為在這段代碼里,divs.length不會動態的改變。
所以為什么動態的NodeLists更快呢?
動態的NodeList返回的更快是因為不需要獲取這個節點的所有信息,而靜態的需要。為了證明這個觀點,可以看一下webkit源碼DynamicNodeList.cpp和StaticNodeList.cpp,這兩種對象創建的方式不一樣。
getElementsByTagName創建的過程不需要做任何操作,只需要返回一個指針即可(此處可以理解為shadow clone)。而querySelectorAll會循環遍歷所有的的結果,然后創建一個新的NodeList(此處可以理解為deep clone)。
結論
getElementsByTagName比querySelectorAll快的原因是因為動態和靜態NodeList的區別。實際在用的過程中取決於你將要做什么,如果你只是查詢一個tag name建議用getElementsByTagName,如果查詢的是一個css selector建議使用querySelectorAll。
