詳解document.getElementById 和 document.querySelector的區別
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 物件 。