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]:
<span class="kd">var</span> <span class="nx">c1</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s1">'.b1 .c'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">c2</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByClassName</span><span class="p">(</span><span class="s1">'c'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">c3</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByClassName</span><span class="p">(</span><span class="s1">'b2'</span><span class="p">)[</span><span class="mi">0</span><span class="p">].</span><span class="nx">getElementsByClassName</span><span class="p">(</span><span class="s1">'c'</span><span class="p">);</span>
需要注意的是,querySelectorAll 所接收的参数是必须严格符合 CSS 选择符规范的。所以下面这种写法,将会抛出异常。代码如下 [4]:
<span class="k">try</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">e1</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByClassName</span><span class="p">(</span><span class="s1">'1a2b3c'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">e2</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s1">'.1a2b3c'</span><span class="p">);</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">message</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">e1</span> <span class="o">&&</span> <span class="nx">e1</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">className</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">e2</span> <span class="o">&&</span> <span class="nx">e2</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">className</span><span class="p">);</span>
(CSS 选择器中的元素名,类和 ID 均不能以数字为开头。)
4. 返回值
大部分人都知道,querySelectorAll 返回的是一个 Static Node List,而 getElementsBy 系列的返回的是一个 Live Node List。
看看下面这个经典的例子 [5]:
<span class="c1">// Demo 1</span>
<span class="kd">var</span> <span class="nx">ul</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s1">'ul'</span><span class="p">)[</span><span class="mi">0</span><span class="p">],</span>
<span class="nx">lis</span> <span class="o">=</span> <span class="nx">ul</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s2">"li"</span><span class="p">);</span>
<span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">lis</span><span class="p">.</span><span class="nx">length</span> <span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">){</span>
<span class="nx">ul</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">"li"</span><span class="p">));</span>
<span class="p">}</span>
<span class="c1">// Demo 2</span>
<span class="kd">var</span> <span class="nx">ul</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="s1">'ul'</span><span class="p">)[</span><span class="mi">0</span><span class="p">],</span>
<span class="nx">lis</span> <span class="o">=</span> <span class="nx">ul</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="s2">"li"</span><span class="p">);</span>
<span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">lis</span><span class="p">.</span><span class="nx">length</span> <span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">){</span>
<span class="nx">ul</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">"li"</span><span class="p">));</span>
<span class="p">}</span>
因为 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 上面是个什么样的情况:
<span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s1">'a'</span><span class="p">).</span><span class="nx">toString</span><span class="p">();</span> <span class="c1">// return "[object NodeList]"</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="s1">'a'</span><span class="p">).</span><span class="nx">toString</span><span class="p">();</span> <span class="c1">// return "[object HTMLCollection]"</span>
这里又多了一个 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]:
<span class="kd">var</span> <span class="nx">ul</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="s1">'ul'</span><span class="p">)[</span><span class="mi">0</span><span class="p">],</span>
<span class="nx">lis1</span> <span class="o">=</span> <span class="nx">ul</span><span class="p">.</span><span class="nx">childNodes</span><span class="p">,</span>
<span class="nx">lis2</span> <span class="o">=</span> <span class="nx">ul</span><span class="p">.</span><span class="nx">children</span><span class="p">;</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">lis1</span><span class="p">.</span><span class="nx">toString</span><span class="p">(),</span> <span class="nx">lis1</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span> <span class="c1">// "[object NodeList]" 11</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">lis2</span><span class="p">.</span><span class="nx">toString</span><span class="p">(),</span> <span class="nx">lis2</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span> <span class="c1">// "[object HTMLCollection]" 4</span>
NodeList 对象会包含文档中的所有节点,如 Element、Text 和 Comment 等。
HTMLCollection 对象只会包含文档中的 Element 节点。
另外,HTMLCollection 对象比 NodeList 对象 多提供了一个 namedItem 方法。
所以在现代浏览器中,querySelectorAll 的返回值是一个静态的 NodeList 对象,而 getElementsBy 系列的返回值实际上是一个 HTMLCollection 对象 。
作者:简生
链接:https://www.zhihu.com/question/24702250/answer/28695133
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
下面为动态NodeList与今天NodeList的区别
昨天,我在雅虎的同事 Scott Schiller (斯科特·席勒, 同时也是SoundManager创造者) 发Twitter询问为何
在所有浏览器上都比
要快好多倍。 有一个 专门的 JSPerf测试页面, 通过对比就能发现两者的速度差异相当明显。 比如作者在Windows XP下使用的 Firefox 3.6.8 浏览器,
比
的运行速度要低98%. 我和 Scott, 以及 YUI团队的 Ryan Grove 有一个活跃的Twitter-sation, 关于这种现象的原因,以及情理之中让人沮丧的结果。 我想好好地解释说明下到底为什么会发生这种情况,以及为什么未来也可能不会改变。
在深入细节之前需要了解这两个方法间一个非常重要的区别,我想说的并不是他们接收的参数一个是标签名,另一个是整个CSS选择器。 而其最大的区别在于返回值的不同:
方法返回一个动态的(live)
, 而
返回的是一个静态的(static)
. 理解这一点是非常必要的.
动态 NodeList
这是文档对象模型(DOM,Document Object Model)中的一个大坑.
对象(以及 HTML DOM 中的
对象)是一种特殊类型的对象. DOM Level 3 spec 规范 对
对象的描述如下:
DOM中的
和
对象是动态的(live); 也就是说,对底层文档结构的修改会动态地反映到相关的集合
和
中。 例如, 如果先获取了某个元素(
)的子元素的动态集合
对象, 然后又在其他地方顺序添加更多子元素到这个DOM父元素中( 可以说添加, 修改, 删除子元素等操作), 这些更改将自动反射到
, 不需要手动进行其他调用. 同样地, 对DOM树上某个
节点的修改,也会实时影响引用了该节点的
和
对象。
方法返回对应标签名的元素的一个动态集合, 只要document发生变化,就会自动更新对应的元素。 因此, 下面的代码实际上是一个死循环:
// XXX 实际中请注意...
// 适当的中间变量是一个好习惯
var divs = document.getElementsByTagName("div");
var i=0;
while(i < divs.length){
document.body.appendChild(document.createElement("div"));
i++;
}
死循环的原因是每次循环都会重新计算
. 每次迭代都会添加一个新的
, 所以每次
,对应的
也在增加, 所以
永远比
小, 循环终止条件也就不会触发[例外情况是dom中没有div,不进入循环]。
你可能会觉得这种动态集合是个坏主意, 但通过动态集合可以保证某些使用非常普遍的对象在各种情况下都是同一个, 如
,
, 以及其他类似的 pre-DOM集合。
静态 NodeList
方法的不同是它返回一个静态的
. 这是表示的 选择器API规范 :
方法返回的
对象必须是静态的, 而不能是动态的([DOM-LEVEL-3-CORE], section 1.1.1). 后续对底层document的更改不能影响到返回的这个
对象. 这意味着返回的对象将包含在创建列表那一刻匹配的所有元素节点。
所以即便是让
和
具有相同的参数和行为, 他们也是有很大的不同点。 在前一种情况下, 返回的
就是方法被调用时刻的文档状态的快照, 而后者总是会随时根据document的状态而更新。 下面的代码就不会是死循环:
var divs = document.querySelectorAll("div"),
i=0;
while(i < divs.length){
document.body.appendChild(document.createElement("div"));
i++;
}
在这种情况下没有死循环,
的值永远不会改变, 所以循环实际上就是将
元素的数量增加一倍, 然后就退出循环。
为什么动态 NodeList 更快呢?
动态
对象在浏览器中可以更快地被创建并返回,因为他们不需要预先获取所有的信息, 而静态
从一开始就需要取得并封装所有相关数据. 再三强调要彻底了解这一点, WebKit 的源码中对每种
类型都有一个单独的源文件: DynamicNodeList.cpp 和 StaticNodeList.cpp. 两种对象类型的创建方式是完全不同的。
对象通过在cache缓存中 注册它的存在 并创建。 从本质上讲, 创建一个新的
是非常轻量级的, 因为不需要做任何前期工作。 每次访问
时, 必须查询 document 的变化, length 属性 以及 item() 方法证明了这一点(使用中括号的方式访问也是一样的).
相比之下,
对象实例由另一个文件创建,然后循环填充所有的数据 。 在 document 中执行静态查询的前期成本上比起
要显著提高很多倍。
如果真正的查看WebKit的源码,你会发现他为
明确地 创建一个返回对象 ,在其中又使用一个循环来获取每一个结果,并创建最终返回的一个
.
结论
速度比
快的根本原因在于动态NodeList和静态NodeList对象的不同。 尽管我可以肯定地说有某种方法来优化这一点, 在获取
时不需要执行很多前期处理操作的动态列表,总比获取静态的集合(返回之前完成各种处理)要快很多。 哪个方法更好用主要还是看你的需求, 如果只是要根据 tag name 来查找元素, 也不需要获取此一个快照, 那就应该使用
方法; 如果需要快照结果(静态),或者需要使用复杂的CSS查询, 则可以考虑
。
原文链接: Why is getElementsByTagName() faster than querySelectorAll()?