相信很多人第一反应当然是这样的了,querySelectorAll的参数是一个css selector,这一步还需要处理呢,肯定会比直接getElementsByTagName要慢了。具体jsperf上有相关的对比,确实getElementsByTagName要比querySelectorAll要快很多,这里不放具体链接了,因为这个网站挂了很长一段时间了已经。
废话不说,直接看原文Why is getElementsByTagName() faster than querySelectorAll()?
这里大概翻译一下,觉得不妥的地方还望指教。
首先这两个方法有个非常明显的区别,一个接受一个tag name的参数,另外一个接受一个css selector。这两个方法最大的不同点是,getElementsByTagName返回的是一个动态的NodeList而querySelectorAll返回的是一个静态的NodeList。
动态的NodeLists
NodeList和HTMLCollection是两个特别的对象类型,3级DOM中是这样定义HTMLCollection:
NodeList和NamedNodeMap在DOM中都是动态的,也就是说,改变这些dom结构都会影响到这些NodeList和NamedNodeMap。例如:如果获取一个有子节点的NodeList,然后增加或者删除子节点,这个NodeList也会动态的改变。同样的,在一个tree结构中改变一个node会影响到所有引用这个node的NodeList和NamedNodeMap对象。
getElementsByTagName方法即返回的就是这样的一个NodeList,看下面代码
var divs = document.getElementsByTagName("div"), i=0; while(i < divs.length){ document.body.appendChild(document.createElement("div")); i++; }
这是一个死循环,因为divs.length在每次循环都会+1。
也许这样的动态设计是一个缺陷,但是开发者已经习惯了这种动态的NodeList。
静态的NodeLists
querySelectorAll返回的是一个静态的NodeList,下面是 Selectors API
querySelectorAll返回的是一个静态的NodeList。改变一个相关node不会改变这个静态NodeList对象。也就是说这个对象必须在获取的时候就被创建出来。
所以即使querySelectorAll返回的结果和getElementsByTagName是一样的,但是他们还是有着本质的区别。在动态NodeList里return的是一个指针,而静态的NodeList返回时当前的node所有信息。
所以下面这个循环就不是一个死循环
var divs = document.querySelectorAll("div"), i=0; while(i < divs.length){ document.body.appendChild(document.createElement("div")); i++; }
因为在这段代码里,divs.length不会动态的改变。
所以为什么动态的NodeLists更快呢?
动态的NodeList返回的更快是因为不需要获取这个节点的所有信息,而静态的需要。为了证明这个观点,可以看一下webkit源码DynamicNodeList.cpp和StaticNodeList.cpp,这两种对象创建的方式不一样。
getElementsByTagName创建的过程不需要做任何操作,只需要返回一个指针即可(此处可以理解为shadow clone)。而querySelectorAll会循环遍历所有的的结果,然后创建一个新的NodeList(此处可以理解为deep clone)。
结论
getElementsByTagName比querySelectorAll快的原因是因为动态和静态NodeList的区别。实际在用的过程中取决于你将要做什么,如果你只是查询一个tag name建议用getElementsByTagName,如果查询的是一个css selector建议使用querySelectorAll。