JavaScript中NodeList与Array的区别及转换方法

作者:像素漫游时间:2026-10-26 00:28:38浏览:606

本文详解JavaScript中NodeList与Array的区别,通过原型链测试证明NodeList并非数组。重点介绍使用Array.prototype.slice.call()将NodeList转换为Array的方法,并针对IE浏览器兼容性提供手动遍历转换及固定长度循环的替代方案,避免动态修改DOM时的死循环问题。

NodeList与Array的基本概念

例如:


代码如下:

 
var anchors = document.getElementsByTagName("a");
for (i = 0; i < anchors.length; i++) {
var ele=anchors[i];//获取单个元素
//执行相关代码
} 

上述代码展示了获取文档中所有链接元素并进行遍历处理的过程。


或许有人疑惑,经此法获取的DOM元素集合岂非数组?它具备length属性,亦可按索引提取单个元素。依大牛所言的鸭子类型理论,若其行为似鸭(拥有length属性)、鸣叫似鸭(支持索引访问),则可视作鸭子。结论显而易见。


若对JavaScript有深入理解,便会知道拥有length属性和索引取值能力并不等同于数组。例如arguments对象也具备这些特征,尽管在实际开发中常将其视为普通数组操作且通常不会出错。


然而,它实际上并非Array类型,而是NodeList。NodeList与数组有着本质区别。


这是否令人惊讶?



验证NodeList非数组的方法

1、NodeList为何不是数组?



验证NodeList是否为数组的最直接方法是尝试调用Array特有的push和pop方法:


代码如下:

 
var anchors = document.getElementsByTagName("a");
var newEle = document.createElement("a");//创建一个新的a元素
anchors.push(newEle);//尝试push
var element= anchors.pop();//尝试pop

您可以自行测试,上述代码在执行push或pop时,均会提示缺少相应方法。这是否足以证明?这种片面的测试可能仍让人心存疑虑。我们可以参照证明arguments非数组的方法,进一步验证NodeList的性质。请看以下代码:


代码如下:

 
Array.prototype.testNodeList = "test nodelist"; //在数组原型上添加属性
function funcNodeList() {
var links = document.getElementsByTagName("a");
alert(links.testNodeList);
} 
function test() {
alert(new Array().testNodeList); //输出 test nodelist
funcNodeList(); //输出 #ff0000? 这是怎么回事?
} 
test(); //执行测试


通过上述分析,我们可以确定NodeList不是Array。那么,如何以操作集合的习惯来处理NodeList呢?



NodeList转换为Array的实践方案

2、像操作Array一样操作NodeList



既然NodeList具备length属性和索引取值能力,将其转换为数组似乎易如反掌。最直接的思路如下:


代码如下:

 
var arr = new Array();
var anchors = document.getElementsByTagName("a")
for (var i = 0; i < anchors.length; i++) {
var ele = anchors[i];
arr.push(ele); //arr即为目标数组
} 

简要说明:先创建一个Array实例,遍历NodeList并将每个元素push到数组变量中,最后操作该数组变量。这种方法是否让您感到智商受辱?


这并非玩笑,以下是从网络搜索到的仅需两行代码即可将NodeList转换为Array的方法:


代码如下:

 
var anchors = document.getElementsByTagName("a");
var arr = Array.prototype.slice.call(anchors); //非IE浏览器下正常运行


然而,令人遗憾的是,上述代码在IE浏览器中无法正常工作,IE会提示缺少JScript对象。


您可能对上述冗长的分析不屑一顾,认为无需将NodeList转换为Array。事实上,在各类编程语言中,类型转换通常被视为不明智的行为,例如C#中的装箱和拆箱操作,不仅涉及性能问题,还容易引发错误。但为何此处要将NodeList视为Array处理?因为在动态修改NodeList时,直接操作可能会无意中触发禁区而未被察觉。举例如下:


(1)、HTML文档片段



<div id="divAnchor">


<a href="http://www.cnblogs.com/jeffwongishandsome/">link test</a>


</div>


(2)、JavaScript测试代码



代码如下:

 
var anchors = document.getElementsByTagName("a");
for (i = 0; i < anchors.length; i++) {
var ele= document.createElement("a");
ele.setAttribute("href", "http://www.cnblogs.com/jeffwongishandsome/");
ele.appendChild(document.createTextNode("new link test"));
document.getElementById("divAnchor").appendChild(ele); //在div中附加新链接
} 

在文档加载完成后执行上述脚本。本意是在div内已存在的a元素后附加一个新a元素。但运行浏览器可能会崩溃?在IE中直接挂起,在FF中提示脚本正忙并询问是否停止。点击停止后,页面内已生成多个a链接。分析原因可知:在for循环中遍历NodeList(前提:循环内部添加了新元素导致NodeList长度动态变化。感谢陈童鞋的建议),其length会不断上升,导致循环陷入死循环。而使用以下代码,则能达到预期效果:


代码如下:

 
var links = document.getElementsByTagName("a");
var anchors = null; //数组
try {
anchors = Array.prototype.slice.call(links);
} 
catch (e) { //兼容IE
anchors = new Array();
for (var i = 0; i < links.length; i++) {
anchors.push(links[i]);
} 
} 
for (i = 0; i < anchors.length; i++) { //数组循环,安全性更高
var ele = document.createElement("a");
ele.setAttribute("href", "http://www.cnblogs.com/jeffwongishandsome/");
ele.appendChild(document.createTextNode("new link test"));
document.getElementById("divAnchor").appendChild(ele); //在div中附加新链接
} 

您可能会问,不转换是否可行?当然可以,只需对熟悉的编码习惯稍作调整即可:


代码如下:

 
var anchors = document.getElementsByTagName("a");
var len = anchors.length; //定义一个变量
for (i = 0; i < len; i++) { //对局部变量len进行循环
var ele = document.createElement("a");
ele.setAttribute("href", "http://www.cnblogs.com/jeffwongishandsome/");
ele.appendChild(document.createTextNode("new link test"));
document.getElementById("divAnchor").appendChild(ele); //在div中附加新链接
} 

至此,无论是否存在疑问,实际编程中的取舍如何,都感谢您的阅读。期待您的指点。


作者:Jeff Wong

作者最新文章