querySelectorAll 方法接收的参数是一个 CSS 选择符(CSS 选择器中的元素名,类和 ID 均不能以数字为开头)。而 getElementsBy 系列接收的参数只能是单一的 className、tagName 或 name 等等。代码如下:
var c1 = document.querySelectorAll('.b1 .c');
var c2 = document.getElementsByClassName('c');
var c3 = document.getElementsByClassName('b2')[0].getElementsByClassName('c');
This is one of the major gotchas of the Document Object Model. The NodeList object (also, the HTMLCollection object in the HTML DOM) is a special type of object. The DOM Level 3 spec says about HTMLCollection objects:
NodeList and NamedNodeMap objects in the DOM are live; that is, changes to the underlying document structure are reflected in all relevant NodeList and NamedNodeMap objects. For example, if a DOM user gets a NodeList object containing the children of an Element, then subsequently adds more children to that element (or removes children, or modifies them), those changes are automatically reflected in the NodeList, without further action on the user’s part. Likewise, changes to a Node in the tree are reflected in all references to that Node in NodeList and NamedNodeMap objects.
The querySelectorAll() method is different because it is a static NodeList instead of a live one. This is indicated in the Selectors API spec:
The NodeList object returned by the querySelectorAll() method must be static, not live ([DOM-LEVEL-3-CORE], section 1.1.1). Subsequent changes to the structure of the underlying document must not be reflected in the NodeList object. This means that the object will instead contain a list of matching Element nodes that were in the document at the time the list was created.
The NodeList object returned by the querySelectorAll() method must be static ([DOM], section 8).
The NodeList object returned by the querySelectorAll() method must be static, not live ([DOM-LEVEL-3-CORE], section 1.1.1). Subsequent changes to the structure of the underlying document must not be reflected in the NodeList object. This means that the object will instead contain a list of matching Element nodes that were in the document at the time the list was created.from
The querySelectorAll(selectors) method, when invoked, must return the static result of running scope-match a selectors string selectors against context object. from
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.
A collection is an object that represents a list of nodes. A collection can be either live or static. Unless otherwise stated, a collection must be live.
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.
A NodeList object is a collection of nodes.from
An HTMLCollection object is a collection of elements.from
实际上,HTMLCollection 和 NodeList 十分相似,都是一个动态的元素集合,每次访问都需要重新对文档进行查询。两者的本质上差别在于,HTMLCollection 是属于 Document Object Model HTML 规范,而 NodeList 属于 Document Object Model Core 规范。简单说,NodeList 是 node 集合,而 HTMLCollection 则是 element 集合,即前者包含后者。
stackoverflow 上也有这个问题 [2]。其中某个答案认为:querySelector is more useful when you want to use more complex selectors. O(∩_∩)O哈哈~ 这也是一个方面吧!
而另外一个回答者 Alvaro Montoro 的观点是:
About the differences, there is an important one in the results between querySelectorAll and getElementsByClassName: the return value is different. querySelectorAll will return a static collection, while getElementsByClassName returns a live collection
A variable generated with querySelectorAll will contain the elements that fulfilled the selector at the moment the method was called.
A variable generated with getElementsByClassName will contain the elements that fulfilled the selector when it is used (that may be different from the moment the method was called).
即通过 querySelectorAll 获取的变量是固定的,仅在 querySelectorAll 被调用的时候,有可能变化;而 getElementsByClassName 获取的变量则是不固定的,可能会在该变量被引用的时候发生变化(因为是 live collection,每次调用都会重新查询 DOM,从而更新变量的值)
Timofey 的观点为:
1.querySelector* is more flexible, as you can pass it any CSS3 selector, not just simple ones for id, tag, or class.
2.*The performance of `querySelectorchanges with the size of the DOM that it is invoked on**. To be precise,querySelectorcalls run in O(n) time andgetElement` calls run in O(1) time, where n is the total number of all children of the element or document it is invoked on. This fact seems to be the least well-known, so I am bolding it.
3.getElement* calls return direct references to the DOM, whereas querySelector* internally makes copies of the selected elements before returning references to them.
Changes to live elements apply immediately - changing a live element changes it directly in the DOM, and therefore the very next line of JS can see that change, and it propagates to any other live elements referencing that element immediately.
Changes to static elements are only written back to the DOM after the current script is done executing. These extra copy and write steps have some small, and generally negligible, effect on performance.
4.The return types of these calls vary. querySelector and getElementById both return a single element. querySelectorAll and getElementsByName both return NodeLists, being newer functions that were added after HTMLCollection went out of fashion. The older getElementsByClassName and getElementsByTagName both return HTMLCollections. Again, this is essentially irrelevant to whether the elements are live or static.
注意:getElementsByName 在 w3c 规范中返回的是 live NodeList,实际测试中也是,不同于 getElementsByClassName 和 getElementsByTagName。
Every element, and the global document, have access to all of these functions except for getElementsByName, which is only implemented on document.
getElementsByTagName 比 querySelectorAll 方法快
Live NodeList objects can be created and returned faster by the browser because they don’t have to have all of the information up front while static NodeLists need to have all of their data from the start. To hammer home the point, the WebKit source code has a separate source file for each type of NodeList: DynamicNodeList.cpp and StaticNodeList.cpp. The two object types are created in very different ways.[3]
The DynamicNodeList object is created by registering its existence in a cache. Essentially, the overheard to creating a new DynamicNodeList is incredibly small because it doesn’t have to do any work upfront. Whenever the DynamicNodeList is accessed, it must query the document for changes, as evidenced by the length property and the item() method (which is the same as using bracket notation).
Compare this to the StaticNodeList object, instances of which are created in another file and then populated with all of the data inside of a loop. The upfront cost to running a query on the document is much more significant than when using a DynamicNodeList instance.
If you take a look at the WebKit source code that actually creates the return value for querySelectorAll(), you’ll see that a loop is used to get every result and build up a NodeList that is eventually returned.
Conclusion
The real reason why getElementsByTagName() is faster than querySelectorAll() is because of the difference between live and static NodeList objects. Although I’m sure there are way to optimize this, doing no upfront work for a live NodeList will generally always be faster than doing all of the work to create a static NodeList. Determining which method to use is highly dependent on what you’re trying to do. If you’re just searching for elements by tag name and you don’t need a snapshot, then getElementsByTagName() should be used; if you do need a snapshot of results or you’re doing a more complex CSS query, then querySelectorAll() should be used.
在《JavaScript 设计模式》第二章 -- "什么是模式" 中提到一个问题:
在页面上选择所有元素并储存,然后过滤集合;
使用浏览器原生的
querySelectorAll()
等功能来选择;使用原生特性
getElementsByClassName()
等功能来获取;然后很自然就会提出另一个问题是:那种方法最快?书中指出是第 3 种方法,比其他方法快 8 到 10 倍。
于是我去 jsperf 测试了一下,发现确实是这样。然后我们自己也可以创建测试文件进行测试:
三次测量结果分别为:
可以看到
getElementsByClassName
比querySelectorAll
快了近 10 倍。顺便我也测试了getElementById
、getElementsByTagName
以及querySelector
。即便是querySelector
也较getElement
系列的慢 2 倍左右。详情可见 demo。所以接下来的问题是:两者有何区别以及为什么产生这样的差异?
区别
在知乎上有这个问题的回答,下面记录一下[1]。
1. W3C 标准
querySelectorAll
属于 W3C 中的 Selectors API 规范。而getElementsBy
系列则属于 W3C 的 DOM 规范。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 选择符(CSS 选择器中的元素名,类和 ID 均不能以数字为开头)。而getElementsBy
系列接收的参数只能是单一的 className、tagName 或 name 等等。代码如下:需要注意的是,
querySelectorAll
所接收的参数是必须严格符合 CSS 选择符规范的。所以下面这种写法,将会抛出异常。代码如下:4. 返回值
querySelectorAll
返回的是一个 static (not live) NodeList,而getElementsBy
系列的返回的是一个 live NodeList(live HTMLCollection),下面我们再具体看看这是什么意思。This is one of the major gotchas of the Document Object Model. The NodeList object (also, the HTMLCollection object in the HTML DOM) is a special type of object. The DOM Level 3 spec says about HTMLCollection objects:
The
querySelectorAll()
method is different because it is a static NodeList instead of a live one. This is indicated in the Selectors API spec:接下来我们再看看下面这个经典的例子(demo3):
因为 Demo 2 中的 lis 是一个动态的 Node List, 每一次调用 lis 都会重新对文档进行查询,导致无限循环的问题。而 Demo 1 中的 lis 是一个静态的 Node List,是一个 li 集合的快照,对文档的任何操作都不会对其产生影响。
但为什么要这样设计呢?其实,在 W3C 规范中对
querySelectorAll
方法有 明确规定:那什么是 NodeList 呢?W3C 中是 这样 说明的:
whatwg 则是这样 说明:
所以,NodeList 本质上是一个动态的 Node 集合,只是规范中对
querySelectorAll
有明确要求,规定其必须返回一个静态的 NodeList 对象。我们再看看在 Chrome 上面是个什么样的情况:这里又多了一个 HTMLCollection 对象出来,那 HTMLCollection 又是什么?
HTMLCollection 在 W3C 的定义如下:
实际上,HTMLCollection 和 NodeList 十分相似,都是一个动态的元素集合,每次访问都需要重新对文档进行查询。两者的本质上差别在于,HTMLCollection 是属于 Document Object Model HTML 规范,而 NodeList 属于 Document Object Model Core 规范。简单说,NodeList 是 node 集合,而 HTMLCollection 则是 element 集合,即前者包含后者。
看看下面的例子会比较好理解(demo4):
NodeList 对象会包含文档中的所有节点,如 Element、Text 和 Comment 等。HTMLCollection 对象只会包含文档中的 Element 节点。另外,HTMLCollection 对象比 NodeList 对象 多提供了一个 namedItem 方法。
总之,在现代浏览器中,
querySelectorAll
的返回值是一个静态的 NodeList 对象,而getElementsBy
系列的返回值实际上是一个动态的 HTMLCollection 对象 。我理解的是 querySelectorAll 返回的是 DOM 的快照,而 getElementsBy 返回的是真实的 DOM(我猜想,querySelectorAll 比 getElementsBy 慢在于遍历的 node 更多???)
stackoverflow 上也有这个问题 [2]。其中某个答案认为:
querySelector
is more useful when you want to use more complex selectors. O(∩_∩)O哈哈~ 这也是一个方面吧!而另外一个回答者 Alvaro Montoro 的观点是:
About the differences, there is an important one in the results between
querySelectorAll
andgetElementsByClassName
: the return value is different.querySelectorAll
will return a static collection, whilegetElementsByClassName
returns a live collectionquerySelectorAll
will contain the elements that fulfilled the selector at the moment the method was called.getElementsByClassName
will contain the elements that fulfilled the selector when it is used (that may be different from the moment the method was called).即通过
querySelectorAll
获取的变量是固定的,仅在querySelectorAll
被调用的时候,有可能变化;而getElementsByClassName
获取的变量则是不固定的,可能会在该变量被引用的时候发生变化(因为是 live collection,每次调用都会重新查询 DOM,从而更新变量的值)Timofey 的观点为:
1.
querySelector*
is more flexible, as you can pass it any CSS3 selector, not just simple ones for id, tag, or class.2.*The performance of `querySelector
changes with the size of the DOM that it is invoked on**. To be precise,
querySelectorcalls run in O(n) time and
getElement` calls run in O(1) time, where n is the total number of all children of the element or document it is invoked on. This fact seems to be the least well-known, so I am bolding it.3.
getElement*
calls return direct references to the DOM, whereasquerySelector*
internally makes copies of the selected elements before returning references to them.querySelecto
r andgetElementById
both return a single element.querySelectorAll
andgetElementsByName
both return NodeLists, being newer functions that were added after HTMLCollection went out of fashion. The oldergetElementsByClassName
andgetElementsByTagName
both return HTMLCollections. Again, this is essentially irrelevant to whether the elements are live or static.getElementsByTagName
比querySelectorAll
方法快Live NodeList objects can be created and returned faster by the browser because they don’t have to have all of the information up front while static NodeLists need to have all of their data from the start. To hammer home the point, the WebKit source code has a separate source file for each type of NodeList: DynamicNodeList.cpp and StaticNodeList.cpp. The two object types are created in very different ways.[3]
The DynamicNodeList object is created by registering its existence in a cache. Essentially, the overheard to creating a new DynamicNodeList is incredibly small because it doesn’t have to do any work upfront. Whenever the DynamicNodeList is accessed, it must query the document for changes, as evidenced by the length property and the item() method (which is the same as using bracket notation).
Compare this to the StaticNodeList object, instances of which are created in another file and then populated with all of the data inside of a loop. The upfront cost to running a query on the document is much more significant than when using a DynamicNodeList instance.
If you take a look at the WebKit source code that actually creates the return value for
querySelectorAll()
, you’ll see that a loop is used to get every result and build up a NodeList that is eventually returned.The real reason why
getElementsByTagName()
is faster thanquerySelectorAll()
is because of the difference between live and static NodeList objects. Although I’m sure there are way to optimize this, doing no upfront work for a live NodeList will generally always be faster than doing all of the work to create a static NodeList. Determining which method to use is highly dependent on what you’re trying to do. If you’re just searching for elements by tag name and you don’t need a snapshot, thengetElementsByTagName()
should be used; if you do need a snapshot of results or you’re doing a more complex CSS query, thenquerySelectorAll()
should be used.参考:
1.querySelectorAll 方法相比 getElementsBy 系列方法有什么区别?
2.querySelector and querySelectorAll vs getElementsByClassName and getElementById in JavaScript
3.Why is getElementsByTagName() faster than querySelectorAll()?
4.Accessing the DOM is not equal accessing the DOM – live vs. static element collections
5.HTMLCollection, NodeList and array of objects