GitHub版本: https://github.com/cncounter/translation/blob/master/tiemao_2014/NodeList/NodeList.md

副标题: 为何getElementsByTagName()比querySelectorAll()快100倍

昨天,我在雅虎的同事 Scott Schiller (斯科特·席勒, 同时也是SoundManager创造者) 发Twitter询问为何getElementsByTagName("a") 在所有浏览器上都比 querySelectorAll("a") 要快好多倍。 有一个 专门的 JSPerf测试页面, 通过对比就能发现两者的速度差异相当明显。 比如作者在Windows XP下使用的 Firefox 3.6.8 浏览器,querySelectorAll("a") 比 getElementsByTagName("a") 的运行速度要低98%. 我和 Scott, 以及 YUI团队的 Ryan Grove 有一个活跃的Twitter-sation, 关于这种现象的原因,以及情理之中让人沮丧的结果。 我想好好地解释说明下到底为什么会发生这种情况,以及为什么未来也可能不会改变。

在深入细节之前需要了解这两个方法间一个非常重要的区别,我想说的并不是他们接收的参数一个是标签名,另一个是整个CSS选择器。 而其最大的区别在于返回值的不同: getElementsByTagName() 方法返回一个动态的(live) NodeList, 而querySelectorAll() 返回的是一个静态的(static) NodeList. 理解这一点是非常必要的.

动态 NodeList

这是文档对象模型(DOM,Document Object Model)中的一个大坑. NodeList 对象(以及 HTML DOM 中的 HTMLCollection对象)是一种特殊类型的对象. DOM Level 3 spec 规范 对 HTMLCollection 对象的描述如下:

DOM中的 NodeList 和 NamedNodeMap 对象是动态的(live); 也就是说,对底层文档结构的修改会动态地反映到相关的集合NodeList 和 NamedNodeMap 中。 例如, 如果先获取了某个元素(Element)的子元素的动态集合 NodeList 对象, 然后又在其他地方顺序添加更多子元素到这个DOM父元素中( 可以说添加, 修改, 删除子元素等操作), 这些更改将自动反射到NodeList, 不需要手动进行其他调用. 同样地, 对DOM树上某个Node节点的修改,也会实时影响引用了该节点的 NodeList和 NamedNodeMap 对象。

getElementsByTagName() 方法返回对应标签名的元素的一个动态集合, 只要document发生变化,就会自动更新对应的元素。 因此, 下面的代码实际上是一个死循环:

// XXX 实际中请注意...
// 适当的中间变量是一个好习惯
var divs = document.getElementsByTagName("div");
var i=0;while(i < divs.length){document.body.appendChild(document.createElement("div"));i++;
}

死循环的原因是每次循环都会重新计算 divs.length. 每次迭代都会添加一个新的 <div>, 所以每次 i++ ,对应的divs.length 也在增加, 所以 i 永远比divs.length小, 循环终止条件也就不会触发[例外情况是dom中没有div,不进入循环]。

你可能会觉得这种动态集合是个坏主意, 但通过动态集合可以保证某些使用非常普遍的对象在各种情况下都是同一个, 如document.images , document.forms, 以及其他类似的 pre-DOM集合。

静态 NodeList

querySelectorAll() 方法的不同是它返回一个静态的 NodeList. 这是表示的 选择器API规范 :

querySelectorAll() 方法返回的 NodeList 对象必须是静态的, 而不能是动态的([DOM-LEVEL-3-CORE], section 1.1.1). 后续对底层document的更改不能影响到返回的这个 NodeList 对象. 这意味着返回的对象将包含在创建列表那一刻匹配的所有元素节点。

所以即便是让 querySelectorAll() 和 getElementsByTagName() 具有相同的参数和行为, 他们也是有很大的不同点。 在前一种情况下, 返回的 NodeList 就是方法被调用时刻的文档状态的快照, 而后者总是会随时根据document的状态而更新。 下面的代码就不会是死循环:

var divs = document.querySelectorAll("div"),i=0;while(i < divs.length){document.body.appendChild(document.createElement("div"));i++;
}

在这种情况下没有死循环, divs.length的值永远不会改变, 所以循环实际上就是将 <div> 元素的数量增加一倍, 然后就退出循环。

为什么动态 NodeList 更快呢?

动态 NodeList 对象在浏览器中可以更快地被创建并返回,因为他们不需要预先获取所有的信息, 而静态 NodeList 从一开始就需要取得并封装所有相关数据. 再三强调要彻底了解这一点, WebKit 的源码中对每种 NodeList 类型都有一个单独的源文件: DynamicNodeList.cpp 和 StaticNodeList.cpp. 两种对象类型的创建方式是完全不同的。

DynamicNodeList 对象通过在cache缓存中 注册它的存在 并创建。 从本质上讲, 创建一个新的 DynamicNodeList 是非常轻量级的, 因为不需要做任何前期工作。 每次访问 DynamicNodeList 时, 必须查询 document 的变化, length 属性 以及 item() 方法证明了这一点(使用中括号的方式访问也是一样的).

相比之下, StaticNodeList 对象实例由另一个文件创建,然后循环填充所有的数据 。 在 document 中执行静态查询的前期成本上比起 DynamicNodeList 要显著提高很多倍。

如果真正的查看WebKit的源码,你会发现他为 querySelectorAll() 明确地 创建一个返回对象 ,在其中又使用一个循环来获取每一个结果,并创建最终返回的一个 NodeList.

结论

getElementsByTagName() 速度比 querySelectorAll() 快的根本原因在于动态NodeList和静态NodeList对象的不同。 尽管我可以肯定地说有某种方法来优化这一点, 在获取NodeList时不需要执行很多前期处理操作的动态列表,总比获取静态的集合(返回之前完成各种处理)要快很多。 哪个方法更好用主要还是看你的需求, 如果只是要根据 tag name 来查找元素, 也不需要获取此一个快照, 那就应该使用 getElementsByTagName()方法; 如果需要快照结果(静态),或者需要使用复杂的CSS查询, 则可以考虑 querySelectorAll()

原文链接: Why is getElementsByTagName() faster than querySelectorAll()?

原文日期: 2010-09-28

翻译日期: 2014-11-13

标签: getElementsByTagName, JavaScript, NodeList, querySelectorAll

DOM中的动态NodeList与静态NodeList相关推荐

  1. Linux中的动态库和静态库(.a/.la/.so/.o)

    为什么80%的码农都做不了架构师?>>>    Linux中的动态库和静态库(.a/.la/.so/.o) Linux中的动态库和静态库(.a/.la/.so/.o) C/C++程序 ...

  2. Unity3D中的动态字体和静态字体

    Unity3D中支持动态字体和静态字体两种格式字体,动态字体即使用TTF格式字体库,静态字体则需要自己打包字体图集.动态字体和静态字体区别在于,动态字体如果出现字体库中不存在的字体,会使用系统字体,而 ...

  3. Linux系统中的“动态库”和“静态库”

    Linux系统中的"动态库"和"静态库" 在Linux操作系统中,普遍使用ELF格式作为可执行程序或者程序生成过程中的中间格式.ELF(Executable a ...

  4. C++ 中的动态库和静态库(Windows)

    库: 在C/C++中,使用库(Library)的技术,可以将编译好的符号提供给第三方使用. 库有两种: 1.动态库 Dynamic-Link Library (DLL)    (Linux下叫做 Sh ...

  5. Linux系统中的动态库和静态库

    来源:  wjlkoorey258 链接:  http://blog.chinaunix.net/uid-23069658-id-3142046.html 今天我们主要来说说Linux系统下基于动态库 ...

  6. Jsp中的动态INCLUDE和静态INCLUDE的区别?

    其实就是动态包含和静态包含的区别: 1. 两者格式不同,静态包含:<%@ include file="文件" %>,而动态包含 < jsp : include p ...

  7. JSP中把动态页面转换为静态页面

    原文:http://www.gpxz.com/diannao/biancheng/261225.html 要自动生成静态页面,用Filter截获jsp请求并先进行预处理,自动生成Html,是个不错的主 ...

  8. ARP缓存记录种类动态条目和静态条目

    ARP缓存记录种类动态条目和静态条目 为使广播量最小,ARP维护IP地址到MAC地址映射的缓存以便将来使用.根据缓存的有效期时间,ARP缓存中包含动态和静态条目本文选自ARP协议全面实战手册. 这里首 ...

  9. 动态库与静态库优缺点比较(转 侵删)

    动态库与静态库优缺点比较 2017-10-09   13:32:01      我们在编写一个C语言程序的时候,经常会遇到好多重复或常用的部分,如果每次都重新编写固然是可以的,不过那样会大大降低工作效 ...

最新文章

  1. INVEST优秀用户故事的特点
  2. 大华web对接文档_分分钟了解Web接口测试
  3. 怎么计算python程序执行时间_【华为云技术分享】计算python程序执行时间
  4. Linux常用命令 一
  5. #openssl #爆重大漏洞heartbleed,危及两亿网民!!!
  6. 怎样呵护友谊_我不善于经营友情,有好的开始却没有好的友谊,怎样建设友情呢?...
  7. 【安全资讯】热门Windows激活软件KMSPico被植入恶意程序
  8. 高通Android camera驱动框架
  9. 可视化实验五:大数据可视化工具—NodeXL
  10. 计算机证件照颜色,更换证件照底色只需10秒,关键还不要钱!这4种方法你一定要学...
  11. 软件测试培训三个月骗局,三个月能学会吗?可以做测试工作吗?
  12. 思科 交换机 全局、telnet、consol 密码配置
  13. win10资源管理器打开一直正在处理文件加载不出来,桌面图标不加载
  14. 五一劳动节,给父母发个红包吧
  15. SMPL模型及源码解读
  16. python散点图图例显示标记点类型_python – matplotlib散点图中的标记点
  17. Excel leftjoin
  18. vc++6.0/使用VisualC++6.0创建MFC基本对话框程序制作数字钟表教程
  19. 第一章 什么是CRM?
  20. 2017高考计算机考试大纲,2017高考全国统一考试大纲及考试说明

热门文章

  1. Smooze for Mac(鼠标增强辅助软件)
  2. python爬虫:爬虫进阶之多线程爬虫
  3. 六大设计原则--开闭原则
  4. pytorch load state dict_pytorch训练trick
  5. WLAN基本知识之802.11标准
  6. 2022 年我作为创作者使用的 5 个强大的 iPad 应用程序
  7. Netty核心功能与线程模型精讲
  8. 2.《你当像鸟飞往你的山》外面有一个世界:去读书吧,你会像鸟飞往你的山!...
  9. Python下对信号的捕获以及优雅的处理
  10. 硬件、网卡驱动官方下载地址