转载链接:DOM中的动态NodeList与静态NodeList
动态NodeList
这是文档对象模型(DOM,Document Object Model)中的一个大坑。NodeList对象(以及HTML DOM中的HTMLCollection对象)是一种特殊类型的对象。DOM Level 3 spec规范对THMLCollection对象的描述如下:
DOM中的NodeList和NameNodeMap对象是动态的(live);也就是说,对底层文档结构的修改会动态地反映到相关的集合NodeList和NameNodeMap中。例如,如果先获取了某个元素(Element)的子元素的动态集合NodeList对象,然后又在其他地方顺序添加更多子元素到这个DOM父元素中(可以说添加,修改,删除子元素等操作),这些更改将自动反射到NodeList,不需要手动进行其他调用。同样的,对DOM树上某个Node节点的修改,也会实时影响引用了该节点的NodeList和NameNodeMap对象。
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.lenght也在增加,所以i永远比divs.length小,循环终止条件也就不会触发【例外情况是dom中没有div,不进入循环】
你可能会觉得这种动态集合是个坏主意,但通过动态集合可以保证某些使用非常普遍的对象在各种情况下都是同一个,如,document.images,document.forms,以及其他类似的pre-DOM集合。
静态NodeList
querySelectorAll()方法的不同是它返回一个静态的NodeList。这是表示的选择器API规范:
querySelectorAll()方法返回的NodeList对象必须是静态的,而不能是动态的([DOM-LEVEL-C-CORE],section 1.1.1)。后续对底层doucment的更改不能影响到返回的这个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时,必须查询doucment的变化,length属性以及item()方法证明了这一点(使用中括号的方式访问也是一样的)。
相比之下,StaticNodeList对象实例由另一个文件创建,然后循环填充所有的数据。在document中执行静态查询的前期成本上比起DynaicNodeList要显著提高很多倍。
如果真正的查看WebKit的源码,你会发现他为querySelectorAll()明确地创建一个返回对象,在其中又使用一个循环来获取每一个结果,并创建最终返回的一个NodeList.
结论:
getElementsByTagName()速度比querySeletorAll()快的根本原因在于动态NodeList和静态NodeList对象的不同。尽管我可以肯定地说有某种方法来优化这一点,在获取NodeList时不需要执行很多前期处理操作的动态列表,总比获取静态的集合(返回之前万恒各种处理)要快得多。哪个方法更好用主要还是看你的需求,如果只是要根据tag name来查找元素,也不需要获取此一个快照,那就应该使用getElementsByTagName()方法;如果需要快照结果(静态),或者需要使用法则的CSS查询,则可以考虑querySeletorAll()