querySelectorAll 方法相比 getElementsBy 系列方法有什麼區別?

querySelectorAll 相比下面這些方法有什麼區別?

  • getElementsByTagName

  • getElementsByClassName

  • getElementsByName


1. W3C 標準

querySelectorAll 屬於 W3C 中的 Selectors API 規範 [1]。而 getElementsBy 系列則屬於 W3C 的 DOM 規範 [2]。

2. 瀏覽器兼容

querySelectorAll 已被 IE 8+、FF 3.5+、Safari 3.1+、Chrome 和 Opera 10+ 良好支持 。

getElementsBy 系列,以最遲添加到規範中的 getElementsByClassName 為例,IE 9+、FF 3 +、Safari 3.1+、Chrome 和 Opera 9+ 都已經支持該方法了。

3. 接收參數

querySelectorAll 方法接收的參數是一個 CSS 選擇符。而 getElementsBy 系列接收的參數只能是單一的className、tagName 和 name。代碼如下 [3]:

var c1 = document.querySelectorAll(".b1 .c");
var c2 = document.getElementsByClassName("c");
var c3 = document.getElementsByClassName("b2")[0].getElementsByClassName("c");

需要注意的是,querySelectorAll 所接收的參數是必須嚴格符合 CSS 選擇符規範的。所以下面這種寫法,將會拋出異常。代碼如下 [4]:

try {
var e1 = document.getElementsByClassName("1a2b3c");
var e2 = document.querySelectorAll(".1a2b3c");
} catch (e) {
console.error(e.message);
}
console.log(e1 e1[0].className);
console.log(e2 e2[0].className);

(CSS 選擇器中的元素名,類和 ID 均不能以數字為開頭。)

4. 返回值

大部分人都知道,querySelectorAll 返回的是一個 Static Node List,而 getElementsBy 系列的返回的是一個 Live Node List。

看看下面這個經典的例子 [5]:

// Demo 1
var ul = document.querySelectorAll("ul")[0],
lis = ul.querySelectorAll("li");
for(var i = 0; i &< lis.length ; i++){ ul.appendChild(document.createElement("li")); } // Demo 2 var ul = document.getElementsByTagName("ul")[0], lis = ul.getElementsByTagName("li"); for(var i = 0; i &< lis.length ; i++){ ul.appendChild(document.createElement("li")); }

因為 Demo 2 中的 lis 是一個動態的 Node List, 每一次調用 lis 都會重新對文檔進行查詢,導致無限循環的問題。

而 Demo 1 中的 lis 是一個靜態的 Node List,是一個 li 集合的快照,對文檔的任何操作都不會對其產生影響。

但為什麼要這樣設計呢?

其實,在 W3C 規範中對 querySelectorAll 方法有明確規定 [6]:

The NodeList object returned by the querySelectorAll() method must be static ([DOM], section 8).

那什麼是 NodeList 呢?

W3C 中是這樣說明的 [7]:

The NodeList interface provides the abstraction of an ordered collection of nodes, without defining or constraining how this collection is implemented. NodeList objects in the DOM are live.

所以,NodeList 本質上是一個動態的 Node 集合,只是規範中對 querySelectorAll 有明確要求,規定其必須返回一個靜態的 NodeList 對象。

我們再看看在 Chrome 上面是個什麼樣的情況:

document.querySelectorAll("a").toString(); // return "[object NodeList]"
document.getElementsByTagName("a").toString(); // return "[object HTMLCollection]"

這裡又多了一個 HTMLCollection 對象出來,那 HTMLCollection 又是什麼?

HTMLCollection 在 W3C 的定義如下 [8]:

An HTMLCollection is a list of nodes. An individual node may be accessed by either ordinal index or the node"s name or id attributes.

Note: Collections in the HTML DOM are assumed to be live meaning that they are automatically updated when the underlying document is changed.

實際上,HTMLCollection 和 NodeList 十分相似,都是一個動態的元素集合,每次訪問都需要重新對文檔進行查詢。兩者的本質上差別在於,HTMLCollection 是屬於 Document Object Model HTML 規範,而 NodeList 屬於 Document Object Model Core 規範。

這樣說有點難理解,看看下面的例子會比較好理解 [9]:

var ul = document.getElementsByTagName("ul")[0],
lis1 = ul.childNodes,
lis2 = ul.children;
console.log(lis1.toString(), lis1.length); // "[object NodeList]" 11
console.log(lis2.toString(), lis2.length); // "[object HTMLCollection]" 4

NodeList 對象會包含文檔中的所有節點,如 Element、Text 和 Comment 等。

HTMLCollection 對象只會包含文檔中的 Element 節點。

另外,HTMLCollection 對象比 NodeList 對象 多提供了一個 namedItem 方法。

所以在現代瀏覽器中,querySelectorAll 的返回值是一個靜態的 NodeList 對象,而 getElementsBy 系列的返回值實際上是一個 HTMLCollection 對象 。

[1] Selectors API Level 2

[2] Document Object Model Core

[3] http://jsbin.com/cuduyigi/1/edit?html,js,console

[4] http://jsbin.com/mabefihi/1/watch?html,js,console

[5]

Demo 1: http://jsbin.com/daduziba/1/watch?html,js,output

Demo 2: http://jsbin.com/xuvodipo/1/watch?html,js,output

[6] Selectors API Level 2

[7] Document Object Model Core

[8] Document Object Model HTML

[9] http://jsbin.com/qijivove/1/watch?html,js,console


@袁婷 對於實時性的解釋是有問題的。

getElementById / querySelector 這兩個獲取到的都是dom節點,結果沒有區別。

getElement* 的實時性體現在返回集合的時候,我們知道getElementsBy*和querySelectorAll返回的都是一個節點集合,類似於數組,兩種方法的區別就在於這個集合會不會自動更新。

//初始時DOMain中沒有&元素
x = document.querySelectorAll("img")
y = document.getElementsByTagName("img")
document.body.appendChild(new Image())
x.length // 0
y.length // 1

另外,getElementById這個只能用在document上,因為正常情況下id是唯一的。


上面說了很多querySelectorAllgetElementsBy實現和原理上面的區別,我這裡拿數據說話,看看兩者在性能上的區別。

所有測試來源於http://jsperf.com/getelementsby-vs-queryselectorall/7,有興趣的朋友可以自己去試試。

  • 硬體環境: Core i5-3570 3.40GHz, Win7 64bit

  • 數據格式:[ Ops/Sec] method

Ops/Sec是指每秒執行操作的次數,越高越好。

  • 瀏覽器1: Chrome 37.0.2062.68 32-bit

[-20,989,532] document.getElementsByTagName("a");
[----166,170] document.querySelectorAll("a");

[-27,659,047] document.getElementsByName("name");
[-----79,022] document.querySelectorAll("[name=name]");

[-22,972,151] document.getElementsByClassName("classname");
[-----67,003] document.querySelectorAll("[class = classname]");

點評:

getElementBy系列的執行速度基本都是querySelectorAll的100+倍

  • 瀏覽器2: Firefox 31.0 32-bit

[226,165,531] document.getElementsByTagName("a");
[----144,576] document.querySelectorAll("a");

[-10,197,651] document.getElementsByName("name");
[----119,340] document.querySelectorAll("[name=name]");

[227,034,307] document.getElementsByClassName("classname");
[----104,720] document.querySelectorAll("[class = classname]");

點評:

Firefox上getElementsByTagNamegetElementsByClassName比Chrome快了10倍,而其餘四個則相差無幾,有高人能解析下原因么?

  • 瀏覽器3: IE 11.0.9600

[--1,797,107] document.getElementsByTagName("a");
[-----59,829] document.querySelectorAll("a");

[--1,344,551] document.getElementsByName("name");
[-----13,706] document.querySelectorAll("[name=name]");

[--1,710,804] document.getElementsByClassName("classname");
[------8,938] document.querySelectorAll("[class = classname]");

點評:

IE比FF和Chrome慢的太多了。不過沒關係:速度不夠,情懷來湊嘛。


普遍觀點是getXXX的性能比querySelectorAll要好很多,對於處理簡單選擇器的情況下非常建議fallback到它們(比如zepto就是這麼做的)。

另外,說一個很逗的「優點」吧

getElementById可以接受一個不合法的id,比如

&&

document.querySelectorAll("#my.name")

毫無疑問是query不到它的,但是用

document.getElementById("my.name")

卻可以

這在維護一些古董代碼(而且還特么是自己改不了的)的時候還是很有用的……


上面的回答寫的都比較清楚了,我這裡補充一點(我認為很關鍵):

querySelectorAll和getElementsBy方法都可以用在elem上,但是querySelectorAll(querySelector也一樣)作用於Element、nodeList、DocumentFragment時的查找範圍還是從整個文檔開始(對於返回的元素會判斷是否在元素的子樹內,將在 element 子樹內的節點組成 NodeList 返回,返回的順序根據文檔順序定義),這一點和getElementsBy是需要區分開的:

(摘自w3c)

&&test&&

var a = document.getElementById("test1");

a.querySelectorAll("div p").length; // 1
a.getElementsByTagName("div p").length; // 0

a.querySelectorAll("#test1 p").length; // 1
a.getElementsByTagName("#test1 p").length; // 0

a.querySelectorAll("html #test1 p").length; // 1
a.getElementsByTagName("html #test1 p").length; // 0

參考資料:

Selectors API Level 1

這有篇3年前的文章,描述的很詳細:

querySelector 和 querySelectorAll 方法瀏覽器實現無誤,避免將其與 JQuery 的選擇器模式混淆


最大的區別還是兼容性吧


推薦閱讀:

DOM真的很慢嗎?如果真的是,為什麼不去改進它?
為什麼DOM不提供insertAfter()方法?
children.length和childElementCount?
如何理解js高程里的document對象是HTMLDocument的實例?

TAG:前端開發 | JavaScript | DOM |