如何快速将JavaScript的NodeList转换为数组?

363
之前在这里回答的问题说这是最快的方法:
//nl is a NodeList
var arr = Array.prototype.slice.call(nl);

在我的浏览器上进行基准测试后,我发现它比这个要慢3倍以上。
var arr = [];
for(var i = 0, n; n = nl[i]; ++i) arr.push(n);

它们都产生相同的输出,但我很难相信我的第二个版本是最快的方式,尤其是因为有人在这里说了其他的话。

这是我的浏览器(Chromium 6)的问题吗?还是有更快的方法?


2
arr[arr.length] = nl[i]; 可能比 arr.push(nl[i]); 更快,因为它避免了函数调用。 - Luc125
9
这个 jsPerf 页面正在追踪此页面上的所有答案:http://jsperf.com/nodelist-to-array/27。 - pilau
请注意,"EDIT2:我找到了一种更快的方法"在IE8上慢了92%。 - Camilo Martin
2
既然你已经知道了有多少节点:var i = nl.length, arr = new Array(i); for(; i--; arr[i] = nl[i]); - mems
@Luc125 这取决于浏览器,因为推送实现可能会进行优化,我正在考虑Chrome,因为v8在这方面做得很好。 - axelduch
15个回答

368

在ES6中,我们现在有一种简单的方式从NodeList创建一个数组:使用Array.from()函数。

// nl is a NodeList
let myArray = Array.from(nl)

这个新的ES6方法在速度上与之前提到的其他方法相比如何? - user5508297
12
比使用切片调用技巧更好。慢于使用for循环,但是我们可能希望在不遍历数组的情况下拥有它。而且语法简洁美观,易于记忆! - webdif
7
Array.from的好处在于你可以使用map参数:console.log(Array.from([1, 2, 3], x => x + x)); // 预期输出:Array [2, 4, 6] - honzajde

256

2021更新:nodeList.forEach()现在是标准,并且在所有当前的浏览器(桌面和移动设备均约95%)中都得到了支持。

所以你可以简单地执行以下操作:

document.querySelectorAll('img').forEach(highlight);

其他情况

如果您因某种原因想将其转换为数组,而不只是迭代它——这是一个完全相关的使用案例——您可以使用ES6中的[...destructuring]Array.from

let array1 = [...mySetOfElements];
// or
let array2 = Array.from(mySetOfElements);

这也适用于其他类数组结构,例如不是NodeList的:

  • HTMLCollection 例如由document.getElementsByTagName返回的。
  • 拥有长度属性和索引元素的对象
  • 可迭代对象(例如MapSet等对象)



过时的2010年回答

在某些浏览器中,第二种方法往往更快,但主要问题是必须使用它,因为第一种方法不跨浏览器。尽管时代在变化。

@kangaxIE 9预览版

Array.prototype.slice现在可以将某些宿主对象(例如NodeList)转换为数组 - 大多数现代浏览器已经能够做到这一点了。

示例:

Array.prototype.slice.call(document.childNodes);

两者都是跨浏览器兼容的--Javascript(至少如果它声称与ECMAscript规范兼容)就是Javascript;Array、prototype、slice和call都是核心语言+对象类型的特性。 - Jason S
6
但它们不能在IE中用于NodeList(我知道很糟糕,但嘿看看我的更新)。 - gblazex
9
因为NodeList不是语言的一部分,而是DOM API的一部分,尤其在IE浏览器中已知存在缺陷/不可预测性。 - gblazex
3
如果考虑到IE8等浏览器,Array.prototype.slice方法不是跨浏览器的。 - Lajos Mészáros
1
是的,这基本上就是我的答案 :) 虽然它在2010年比今天(2015年)更相关。 - gblazex
1
这可以缩短为 [].slice.call(document.childNodes) :) 哦,美好的日子。 - George

121

10
在 TypeScript 中对我没有起作用。 ERROR TypeError: el.querySelectorAll(...).slice 不是一个函数。 - Alireza Mirian
1
在Chrome 71中,使用3个unshift方法之后,该方法排名第三快:https://jsperf.com/nodelist-to-array/90 - BrianFreud

27

在ES6中,你可以使用以下两种方式来处理:

  • Array.from

    let array = Array.from(nodelist)

  • 扩展运算符

    let array = [...nodelist]


3
之前的回答已经包含了所有必要的内容,这个回答不会增加任何新信息。 - Stefan Becker

19

一些优化:

  • 将 NodeList 的长度保存在变量中。
  • 在设置新数组之前,显式地设置新数组的长度。
  • 访问索引,而不是使用 push 或 unshift。

代码 (jsPerf):

var arr = [];
for (var i = 0, ref = arr.length = nl.length; i < ref; i++) {
 arr[i] = nl[i];
}

1
你可以通过使用Array(length)而不是创建数组然后单独调整大小来节省一点时间。如果您使用const声明数组及其长度,并在循环内部使用let,则比上述方法快约1.5%:const a = Array(nl.length), c = a.length; for (let b = 0; b < c; b++) { a[b] = nl[b]; }请参见https://jsperf.com/nodelist-to-array/93 - BrianFreud

15
结果完全取决于浏览器,为了得出客观的结论,我们需要进行一些性能测试。下面是一些结果,你可以在此处运行它们:http://jsbin.com/oqeda/3/edit
Chrome 6: Firefox 3.6: Firefox 4.0b2:

Safari 5:

IE9平台预览版3:


1
我想知道反向循环与这些循环相比如何表现... for (var i=o.length; i--;) ... 在这些测试中,'for循环'是否在每次迭代中重新评估了长度属性? - Dagg Nabbit

15

最快和跨浏览器的是

for(var i=-1,l=nl.length;++i!==l;arr[i]=nl[i]);

正如我在比较中所述

http://jsbin.com/oqeda/98/edit

*感谢@CMS提供的想法!

Chromium (Similar to Google Chrome) Firefox Opera


1
链接似乎有误,应该是91,而不是89,以包含您提到的测试。而98似乎是最完整的一个选择。 - Yaroslav Yakovlev

10
假设nodeList = document.querySelectorAll("div"),这是将nodelist转换为数组的简明形式。
var nodeArray = [].slice.call(nodeList);

在这里看我使用它 这里


7
NodeList.prototype.forEach = Array.prototype.forEach;

现在,您可以执行document.querySelectorAll('div').forEach(function()...)。

好主意,谢谢 @John!不过,NodeList 不起作用,但 Object 可以:Object.prototype.forEach = Array.prototype.forEach; document.getElementsByTagName("img").forEach(function(img) { alert(img.src); }); - Ian Campbell
3
不要使用Object.prototype:它会破坏JQuery和许多其他东西,如字典字面量语法。 - Nate Symer
确保避免扩展本地内置函数。 - roland

5
更快更简短:
// nl is the nodelist
var a=[], l=nl.length>>>0;
for( ; l--; a[l]=nl[l] );

3
为什么要加上>>>0?为什么不将赋值语句放在for循环的第一部分? - Camilo Martin
5
这段代码有问题。当l等于0时,循环将会结束,因此第0个元素不会被复制(记得数组中有一个0索引的元素)。 - Camilo Martin
1
喜欢这个答案,但是......如果有人想知道:>>> 可能在这里不是必需的,但是被用来保证 nodelist 的长度符合数组规范;它确保它是一个无符号的 32 位整数。在这里查看 http://www.ecma-international.org/ecma-262/5.1/#sec-15.4 如果你喜欢难以阅读的代码,请使用 @CamiloMartin 的建议! - Todd
针对@CamiloMartin的回复-在for循环的第一部分中放置var是有风险的,因为存在'变量提升'。即使var行出现在下方的某个地方,也会将其声明“提升”到函数顶部,并且这可能会导致从代码序列中不容易看出的副作用。例如,在for循环之前发生在同一函数中的某些代码可能依赖于未声明的变量al。因此,为了更可预测性,请在函数顶部声明您的变量(或者如果使用ES6,则改用不会提升的constlet)。 - brennanyoung

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接