将JavaScript NodeList转换为数组的最快方法?


251

先前在这里回答的问题说这是最快的方法:

//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);

它们都产生相同的输出,但是我很难相信我的第二个版本是最快的方法,特别是因为人们在这里另有说明。

这是我的浏览器中的一个怪胎(铬6)吗?还是有更快的方法?

编辑:对于任何关心的人,我都选择了以下内容(这似乎是我测试过的每个浏览器中最快的):

//nl is a NodeList
var l = []; // Will hold the array of Node's
for(var i = 0, ll = nl.length; i != ll; l.push(nl[i++]));

EDIT2:我发现了更快的方法

// nl is the nodelist
var arr = [];
for(var i = nl.length; i--; arr.unshift(nl[i]));

2
arr[arr.length] = nl[i];可能比arr.push(nl[i]);避免调用函数要快。
2011年

9
该jsPerf页面跟踪该页面上的所有答案:jsperf.com/nodelist-to-array/27
pilau,

请注意,在IE8上,“ EDIT2:我找到了一种更快的方法”要慢92%。
卡米洛·马丁

2
由于您已经知道自己有多少个节点:var i = nl.length, arr = new Array(i); for(; i--; arr[i] = nl[i]);
mems,2014年

@ Luc125取决于浏览器,因为可以优化推送实现,所以我考虑使用chrome,因为v8可以很好地处理此类问题。
axelduch 2014年

Answers:


197

在某些浏览器中,第二个趋向于更快,但要点是必须使用它,因为第一个不是跨浏览器。即使时代是改变

@kangax IE 9预览

Array.prototype.slice现在可以将某些宿主对象(例如,NodeList的)转换为数组-大多数现代浏览器已经能够完成相当长的一段时间了。

例:

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

??? 两者都是跨浏览器兼容的-Javascript(至少,如果声称与ECMAscript规范兼容)是Javascript;数组,原型,切片和调用都是核心语言+对象类型的全部功能。
杰森·S

6
但它们无法在IE中的NodeLists上使用(我知道它很烂,但是嘿,请看我的更新)
gblazex

9
因为NodeLists不是语言的一部分,所以它们是DOM API的一部分,DOM API已知是错误的/无法预测的,尤其是在IE中
gblazex

3
如果您考虑IE8,则Array.prototype.slice不是跨浏览器。
Lajos Meszaros

1
是的,这基本上是我的答案:)尽管与今天(2015年)相比,2010年它的意义更大。
gblazex

224

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

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

与上面提到的其他方法相比,这种新的ES6方法的速度如何?
user5508297 '16

10
@ user5508297比切片调用技巧更好。它比for循环要慢,但这不完全是我们可能想要一个没有遍历它的数组。语法漂亮,简单且易于记忆!
webdif '16

Array.from的好处是可以使用map参数:console.log(Array.from([1, 2, 3], x => x + x)); // expected output: Array [2, 4, 6]
honzajde


19

一些优化:

  • 将NodeList的长度保存在变量中
  • 在设置之前,显式设置新数组的长度。
  • 访问索引,而不是推动或保持不变。

代码(jsPerf):

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

可以剃一个通过使用阵列(长度),而不是创建阵列,然后分别浆纱它更多的时间。如果然后使用const声明数组及其长度,并在循环内使用let,则其结束速度将比上述方法快1.5%:const a = Array(nl.length),c = a.length; for(let b = 0; b <c; b ++){a [b] = nl [b]; }参见jsperf.com/nodelist-to-array/93
BrianFreud

15

结果将完全取决于浏览器,以给出客观的判断,我们必须进行一些性能测试,这里有一些结果,您可以在这里运行它们:

Chrome 6:

Firefox 3.6:

Firefox 4.0b2:

Safari 5:

IE9平台预览3:


1
我想知道反向for循环如何抵御这些问题…… for (var i=o.length; i--;)这些测试中的“ for循环”是否在每次迭代中重新评估了length属性?
Dagg Nabbit 2010年


9

在ES6中,您可以使用:

  • Array.from

    let array = Array.from(nodelist)

  • 点差运算符

    let array = [...nodelist]


不添加以前答案中已写的新内容。
Stefan Becker

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

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


好主意,谢谢@约翰!但是,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 2015年

当然,请避免扩展本机内置函数。
罗兰

5

更快和更短:

// nl is the nodelist
var a=[], l=nl.length>>>0;
for( ; l--; a[l]=nl[l] );

3
为什么>>>0呢?为什么不将赋值放在for循环的第一部分?
卡米洛·马丁

5
另外,这是越野车。当l为is时0,循环将结束,因此0不会复制th元素(注意index处有一个元素0
Camilo Martin

1
喜欢这个答案,但是...任何想知道的人:这里>>> 可能并不需要,但是可以用来保证节点列表的长度符合数组规范;它确保它是一个无符号的32位整数。ecma-international.org/ecma-262/5.1/#sec-15.4在此处查看如果您喜欢不可读的代码,请将此方法与@CamiloMartin的建议一起使用!
2014年

回复@CamiloMartin- 由于“可变提升” ,将其放入循环var的第一部分很冒险forvar即使该var行出现在函数下方,声明的位置也会“悬挂”在函数的顶部,这可能导致从代码序列中不明显的副作用。例如在同一个函数的一些代码发生之前的for循环可能会依赖al被宣告。因此,为了获得更大的可预测性,请在函数顶部声明您的var(或者,如果在ES6上,请使用constlet替代,不要将其吊起)。
brennanyoung

3

在这里查看有关同一件事的博客文章。根据我的收集,多余的时间可能与在范围链上行走有关。


有趣。我现在刚刚进行了一些类似的测试,并且Firefox 3.6.3的运行速度没有任何提高,而Opera 10.6的增长了20%,Chrome 6的手动迭代推动了230%(!)的增长。
jairajs89

@ jairajs89很奇怪。看来Array.prototype.slice取决于浏览器。我想知道每个浏览器使用什么算法。
Vivin Paliath'7

3

这是我在JS中使用的功能:

function toArray(nl) {
    for(var a=[], l=nl.length; l--; a[l]=nl[l]);
    return a;
}

1

以下是截至发布日期更新的图表(“未知平台”图表为Internet Explorer 11.15.16299.0):

Safari 11.1.2 Firefox 61.0 铬68.0.3440.75 Internet Explorer 11.15.16299.0

从这些结果来看,预分配1方法似乎是最安全的跨浏览器投注。


1

假设nodeList = document.querySelectorAll("div"),这是转换nodelist为数组的简洁形式。

var nodeArray = [].slice.call(nodeList);

看到我在这里用它。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.