Array.from()与展开语法的区别

94

使用Array.from(document.querySelectorAll('div'))[...document.querySelectorAll('div')]是否有区别?

以下是一个例子:

let spreadDivArray = [...document.querySelectorAll('div')];
console.log(spreadDivArray);

let divArrayFrom = Array.from(document.querySelectorAll('div'));
console.log(divArrayFrom);

console.log()将记录相同的结果。

有性能差异吗?


扩展运算符的好处是它支持Object。至于性能,我不知道。 - Semi-Friends
为了找出是否存在性能差异,请运行基准测试。结果可能会有很大的不同,这取决于您是在本地ES6环境中还是转换为ES5。 - user663031
7
主要区别在于 Array.from 可以处理类数组对象,这些对象没有实现迭代器协议(即 Symbol.iterator)。即使使用了 ES6 和新的浏览器规范,这种对象也越来越少见。 - nils
5
... 不是一个运算符! - Felix Kling
除了性能外,这些数组处理程序可能存在不同的大小上限。在至少 Chrome 上,当使用非常大的数组时,展开运算符似乎会抛出“调用堆栈大小超过最大值”的错误,而 Array.from() 则正常工作。 - peterflynn
6个回答

91

Spread element或者语法(请注意它不是操作符)仅适用于可迭代的对象(即实现@@iterator方法的对象)。Array.from()同样适用于非可迭代的类数组对象(即具有索引元素和length属性的对象)。

看下面的例子:

const arrayLikeObject = { 0: 'a', 1: 'b', length: 2 };

// This logs ['a', 'b']
console.log(Array.from(arrayLikeObject));

// This throws TypeError: arrayLikeObject[Symbol.iterator] is not a function
console.log([...arrayLikeObject]);

此外,如果你仅需要将某些内容转换为数组,使用Array.from()可能更易读。展开操作符在某些情况下也很有用,例如当你想要连接多个数组(['a', 'b', ...someArray, ...someOtherArray])。

8
我认为Array.from()虽然易于阅读,但是对于人们来说,扩展运算符语法...arrayLikeObject同样易读(甚至更易读)。 - qarthandso
2
还要注意,扩展语法(...arrayLikeObject)更短。虽然有时这可能是一个因素,但也许不应该是。 - trysis
如果我们要扩展到新的(不同的)数组中,那么我会同意。但是如果我们需要复制一个数组(到完全相同的数组),那么Array.from看起来更有吸引力,至少在某些情况下更易读,例如当我们需要将Array.prototype.reduce的起始值传递为数组时,我们调用它。 - Eduard
2
警告:像 var i = 5; Array.from(i) 这样的数字参数会导致 [],而 var i = 5; [i] 则会得到 [5] - Josh Stodola
@Eduard "Array.from 看起来更吸引人" 是什么让编程语法更吸引人呢? - undefined

12

Array.from是一个静态方法,也就是说它是一个函数,而spread语法则是数组字面量语法的一部分。你可以像传递数据一样传递函数,可以调用它们一次、多次或者不调用它们。而这在spread语法中是不可能的,因为在这方面它是静态的。

另一个区别,正如@nils已经指出的那样,Array.from还可以与类似数组的对象一起使用,这些对象没有实现可迭代协议,而spread则需要实现可迭代协议。


2
Array.from也适用于类似数组的对象,这些对象没有实现可迭代协议。你能举一个这样的对象的例子吗? - mpen

6
区别在于使用spread ...语法可以将一个数组展开,而Array.from()则直接创建一个新的数组
举个例子,[...x]通过使用spread语法将现有数组扩展到数组文字语法中创建了一个新的数组 - 方括号[]实际上是正在执行数组创建操作的内容。这些方括号可以与任意数量的其他语法交换以实现其他结果(例如,展开到函数参数foo(...x)、展开到对象{...x}、使用多个展开连接数组[...x, ...y]等)。 .from()不会对任何内容进行展开,它总是基于提供的数据创建一个新的数组。

21
数组字面量总是创建一个新的数组… - Bergi
4
我不确定我是否误解了你的措辞,但是你是在暗示扩展运算符会改变数组而不是创建一个新的吗? - Bergi
4
扩展运算符并不会创建一个数组。在OP的例子中,数组是通过包围扩展运算符的方括号创建的。 - James Donnelly
4
啊,好的,我只是想确保你指的是正确的事情。也许如果你说“扩展数组字面量”而不是“扩展数组”,会更有帮助,因为它不适用于任意数组。 - Bergi
5
澄清一下,...foo 语法会将数组中的所有值展开(扩展),就好像它们是单独的、以逗号分隔的参数一样。外面的 [] 则是用于创建一个新数组。因此,[...foo] 将创建一个新数组,并通过展开所有数组元素作为数组构造函数参数来填充它,并对每个元素进行完全复制。而 Array.from(foo) 则使用输入变量创建一个新数组,速度更快,因为它只做了浅拷贝(这很快)。 - Mitch McMabers
显示剩余2条评论

3
如果输入是可迭代的,它们会执行完全相同的操作。但是,根据基准测试,针对Set对象,展开运算符似乎表现更佳。 https://jsben.ch/5lKjg

let set = new Set();
for (let i = 0; i < 10000; i++) {
  set.add(Math.random());
}


let tArrayFrom = window.performance.now()

let arr = Array.from(set)

console.log("Array.from():", window.performance.now() - tArrayFrom + "ms")


// slightly faster in most of the runs:
let tSpread = window.performance.now()

let arr2 = [...set];

console.log("Spread syntax:", window.performance.now() - tSpread + "ms")


1
在Chrome上,截至今天,使用这个基准测试,Array.from()似乎比spread更快一些。 - peterflynn
对我来说,使用扩展语法会快几毫秒。 - Quinten C

2

使用Babel是查看内部发生情况的好方法。

注意,确保在Babel中选择了最新版本,因为默认版本是错误的。

使用上面的示例,这是输出结果。

function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }

var spreadDivArray = [].concat(_toConsumableArray(document.querySelectorAll('div')));
console.log(spreadDivArray);

var divArrayFrom = Array.from(document.querySelectorAll('div'));
console.log(divArrayFrom);

2
如果节点列表不是可连接的,[].concat 似乎无法工作?这是 Babel 的输出吗? - Bergi
“这是 Babel 的输出吗?” “确实是,我刚刚把代码复制到了 babeljs.io 上,你有例子吗?也许在需要时 Babel 还会进行其他转换。” 当然,这只是针对这个特定情况的测试。 - Keith
3
babeljs.io的REPL存在一些奇怪的选项,不是很可靠。使用[].concat是一种不正确的简化(仅在数组参数上执行与扩展语法相同的操作),这可能是由于Babel中的错误或某些未知设置引起的。 - Bergi
1
啊,点击Babel中的“latest”确实有所不同。我会更新答案,并提供新的输出。感谢您的提示。 - Keith
2
更不用说,当你查看Babel转译后的代码时,“内部发生了什么”是看不到的。运行时内部发生的事情完全是另一回事。 - Mörre
显示剩余2条评论

-3

我需要澄清大家的答案:

  • ...foo 语法只是将所有数组值展开(扩展),就好像它们是单独的、逗号分隔的参数一样。它进行了浅层展开。任何基本类型(数字、字符串等)都会被复制,而任何复杂类型(对象)则会被引用。
  • 方括号 [] 则是创建一个新数组。
  • 因此,[...foo] 将创建一个新数组,并通过执行浅层复制展开所有数组元素,就好像它们是数组构造函数参数一样,然后将所有复制的元素放入新数组中。
  • Array.from(foo) 将使用输入变量创建一个新数组,但速度要快得多,因为它仅创建一个浅层副本(这更快)。因此,它采用精确的输入,只是将每个变量/引用放入新数组中。
  • 使用 Array.from()

4
这是不正确的,无论是扩展操作符还是array.from()方法都只会创建浅拷贝。 - Carl Bäckström

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