传播运算符 vs array.concat()
spread operator vs array.concat()
spread operator
和array.concat()
有什么区别
let parts = ['four', 'five'];
let numbers = ['one', 'two', 'three'];
console.log([...numbers, ...parts]);
Array.concat()
函数
let parts = ['four', 'five'];
let numbers = ['one', 'two', 'three'];
console.log(numbers.concat(parts));
Both results are same. So, what kind of scenarios we want to use them? And which one is best for performance?
嗯console.log(['one', 'two', 'three', 'four', 'five'])
也有相同的结果,那么为什么要在这里使用呢? :P
通常,当您有两个(或更多)来自任意来源的数组时,您会使用 concat
,并且如果附加元素始终是数组的一部分,您将在数组文字中使用扩展语法之前是已知的。因此,如果您的代码中有一个带有 concat
的数组文字,只需使用扩展语法,否则只需使用 concat
:
[...a, ...b] // bad :-(
a.concat(b) // good :-)
[x, y].concat(a) // bad :-(
[x, y, ...a] // good :-)
在处理 non-array 值时,这两种选择的行为也大不相同。
我认为有效的一个区别是,对大数组大小使用扩展运算符会给您带来 Maximum call stack size exceeded
错误,您可以使用 concat
运算符避免这种情况。
var someArray = new Array(600000);
var newArray = [];
var tempArray = [];
someArray.fill("foo");
try {
newArray.push(...someArray);
} catch (e) {
console.log("Using spread operator:", e.message)
}
tempArray = newArray.concat(someArray);
console.log("Using concat function:", tempArray.length)
当参数不是数组时,concat
和点差非常不同。
当参数不是数组时,concat
将其作为一个整体添加,而 ...
尝试迭代它,如果不能则失败。考虑:
a = [1, 2, 3]
x = 'hello';
console.log(a.concat(x)); // [ 1, 2, 3, 'hello' ]
console.log([...a, ...x]); // [ 1, 2, 3, 'h', 'e', 'l', 'l', 'o' ]
此处,concat
以原子方式处理字符串,而 ...
使用其默认迭代器 char-by-char。
另一个例子:
x = 99;
console.log(a.concat(x)); // [1, 2, 3, 99]
console.log([...a, ...x]); // TypeError: x is not iterable
同样,对于 concat
数字是一个原子,...
尝试迭代它但失败了。
最后:
function* gen() { yield *'abc' }
console.log(a.concat(gen())); // [ 1, 2, 3, Object [Generator] {} ]
console.log([...a, ...gen()]); // [ 1, 2, 3, 'a', 'b', 'c' ]
concat
不尝试迭代生成器并将其作为一个整体附加,而 ...
很好地从中获取所有值。
综上所述,当你的参数可能是non-arrays时,选择concat
和...
取决于你是否希望它们被迭代。
上面描述了 concat
的默认行为,然而,ES6 provides a way to override it with Symbol.isConcatSpreadable
。默认情况下,此符号对于数组为 true
,对于其他所有内容为 false
。将其设置为 true
告诉 concat
迭代参数,就像 ...
所做的那样:
str = 'hello'
console.log([1,2,3].concat(str)) // [1,2,3, 'hello']
str = new String('hello');
str[Symbol.isConcatSpreadable] = true;
console.log([1,2,3].concat(str)) // [ 1, 2, 3, 'h', 'e', 'l', 'l', 'o' ]
Performance-wise concat
更快,可能是因为它可以受益于 array-specific 优化,而 ...
必须符合通用迭代协议。计时:
let big = (new Array(1e5)).fill(99);
let i, x;
console.time('concat-big');
for(i = 0; i < 1e2; i++) x = [].concat(big)
console.timeEnd('concat-big');
console.time('spread-big');
for(i = 0; i < 1e2; i++) x = [...big]
console.timeEnd('spread-big');
let a = (new Array(1e3)).fill(99);
let b = (new Array(1e3)).fill(99);
let c = (new Array(1e3)).fill(99);
let d = (new Array(1e3)).fill(99);
console.time('concat-many');
for(i = 0; i < 1e2; i++) x = [1,2,3].concat(a, b, c, d)
console.timeEnd('concat-many');
console.time('spread-many');
for(i = 0; i < 1e2; i++) x = [1,2,3, ...a, ...b, ...c, ...d]
console.timeEnd('spread-many');
我只回答性能问题,因为已经有关于场景的很好的答案。我编写了一个测试并在最新的浏览器上执行了它。下面是结果和代码。
/*
* Performance results.
* Browser Spread syntax concat method
* --------------------------------------------------
* Chrome 75 626.43ms 235.13ms
* Firefox 68 928.40ms 821.30ms
* Safari 12 165.44ms 152.04ms
* Edge 18 1784.72ms 703.41ms
* Opera 62 590.10ms 213.45ms
* --------------------------------------------------
*/
下面是我写的和使用的代码
const array1 = [];
const array2 = [];
const mergeCount = 50;
let spreadTime = 0;
let concatTime = 0;
// Used to popolate the arrays to merge with 10.000.000 elements.
for (let i = 0; i < 10000000; ++i) {
array1.push(i);
array2.push(i);
}
// The spread syntax performance test.
for (let i = 0; i < mergeCount; ++i) {
const startTime = performance.now();
const array3 = [ ...array1, ...array2 ];
spreadTime += performance.now() - startTime;
}
// The concat performance test.
for (let i = 0; i < mergeCount; ++i) {
const startTime = performance.now();
const array3 = array1.concat(array2);
concatTime += performance.now() - startTime;
}
console.log(spreadTime / mergeCount);
console.log(concatTime / mergeCount);
虽然有些回复在谈到大数组的性能时是正确的,但在处理小数组时性能却大不相同。
您可以在 https://jsperf.com/spread-vs-concat-size-agnostic 上自行查看结果。
如您所见,spread
对于较小的数组要快 50%,而 concat
对于大数组要快好几倍。
concat
和 push
之间有一个非常重要的区别,前者 不会 改变底层数组,要求您分配结果到相同或不同的数组:
let things = ['a', 'b', 'c'];
let moreThings = ['d', 'e'];
things.concat(moreThings);
console.log(things); // [ 'a', 'b', 'c' ]
things.push(...moreThings);
console.log(things); // [ 'a', 'b', 'c', 'd', 'e' ]
我已经看到由 concat
更改数组(为朋友交谈;)的假设引起的错误。
@georg 的回答有助于查看比较。我也很好奇 .flat() 在 运行 中如何比较,这是迄今为止最糟糕的。如果速度优先,请不要使用 .flat() 。 (直到现在我才意识到这一点)
let big = new Array(1e5).fill(99);
let i, x;
console.time("concat-big");
for (i = 0; i < 1e2; i++) x = [].concat(big);
console.timeEnd("concat-big");
console.time("spread-big");
for (i = 0; i < 1e2; i++) x = [...big];
console.timeEnd("spread-big");
console.time("flat-big");
for (i = 0; i < 1e2; i++) x = [[], big].flat();
console.timeEnd("flat-big");
let a = new Array(1e3).fill(99);
let b = new Array(1e3).fill(99);
let c = new Array(1e3).fill(99);
let d = new Array(1e3).fill(99);
console.time("concat-many");
for (i = 0; i < 1e2; i++) x = [1, 2, 3].concat(a, b, c, d);
console.timeEnd("concat-many");
console.time("spread-many");
for (i = 0; i < 1e2; i++) x = [1, 2, 3, ...a, ...b, ...c, ...d];
console.timeEnd("spread-many");
console.time("flat-many");
for (i = 0; i < 1e2; i++) x = [1, 2, 3, a, b, c, d].flat();
console.timeEnd("flat-many");
spread operator
和array.concat()
有什么区别
let parts = ['four', 'five'];
let numbers = ['one', 'two', 'three'];
console.log([...numbers, ...parts]);
Array.concat()
函数
let parts = ['four', 'five'];
let numbers = ['one', 'two', 'three'];
console.log(numbers.concat(parts));
Both results are same. So, what kind of scenarios we want to use them? And which one is best for performance?
嗯console.log(['one', 'two', 'three', 'four', 'five'])
也有相同的结果,那么为什么要在这里使用呢? :P
通常,当您有两个(或更多)来自任意来源的数组时,您会使用 concat
,并且如果附加元素始终是数组的一部分,您将在数组文字中使用扩展语法之前是已知的。因此,如果您的代码中有一个带有 concat
的数组文字,只需使用扩展语法,否则只需使用 concat
:
[...a, ...b] // bad :-(
a.concat(b) // good :-)
[x, y].concat(a) // bad :-(
[x, y, ...a] // good :-)
在处理 non-array 值时,这两种选择的行为也大不相同。
我认为有效的一个区别是,对大数组大小使用扩展运算符会给您带来 Maximum call stack size exceeded
错误,您可以使用 concat
运算符避免这种情况。
var someArray = new Array(600000);
var newArray = [];
var tempArray = [];
someArray.fill("foo");
try {
newArray.push(...someArray);
} catch (e) {
console.log("Using spread operator:", e.message)
}
tempArray = newArray.concat(someArray);
console.log("Using concat function:", tempArray.length)
concat
和点差非常不同。
当参数不是数组时,concat
将其作为一个整体添加,而 ...
尝试迭代它,如果不能则失败。考虑:
a = [1, 2, 3]
x = 'hello';
console.log(a.concat(x)); // [ 1, 2, 3, 'hello' ]
console.log([...a, ...x]); // [ 1, 2, 3, 'h', 'e', 'l', 'l', 'o' ]
此处,concat
以原子方式处理字符串,而 ...
使用其默认迭代器 char-by-char。
另一个例子:
x = 99;
console.log(a.concat(x)); // [1, 2, 3, 99]
console.log([...a, ...x]); // TypeError: x is not iterable
同样,对于 concat
数字是一个原子,...
尝试迭代它但失败了。
最后:
function* gen() { yield *'abc' }
console.log(a.concat(gen())); // [ 1, 2, 3, Object [Generator] {} ]
console.log([...a, ...gen()]); // [ 1, 2, 3, 'a', 'b', 'c' ]
concat
不尝试迭代生成器并将其作为一个整体附加,而 ...
很好地从中获取所有值。
综上所述,当你的参数可能是non-arrays时,选择concat
和...
取决于你是否希望它们被迭代。
上面描述了 concat
的默认行为,然而,ES6 provides a way to override it with Symbol.isConcatSpreadable
。默认情况下,此符号对于数组为 true
,对于其他所有内容为 false
。将其设置为 true
告诉 concat
迭代参数,就像 ...
所做的那样:
str = 'hello'
console.log([1,2,3].concat(str)) // [1,2,3, 'hello']
str = new String('hello');
str[Symbol.isConcatSpreadable] = true;
console.log([1,2,3].concat(str)) // [ 1, 2, 3, 'h', 'e', 'l', 'l', 'o' ]
Performance-wise concat
更快,可能是因为它可以受益于 array-specific 优化,而 ...
必须符合通用迭代协议。计时:
let big = (new Array(1e5)).fill(99);
let i, x;
console.time('concat-big');
for(i = 0; i < 1e2; i++) x = [].concat(big)
console.timeEnd('concat-big');
console.time('spread-big');
for(i = 0; i < 1e2; i++) x = [...big]
console.timeEnd('spread-big');
let a = (new Array(1e3)).fill(99);
let b = (new Array(1e3)).fill(99);
let c = (new Array(1e3)).fill(99);
let d = (new Array(1e3)).fill(99);
console.time('concat-many');
for(i = 0; i < 1e2; i++) x = [1,2,3].concat(a, b, c, d)
console.timeEnd('concat-many');
console.time('spread-many');
for(i = 0; i < 1e2; i++) x = [1,2,3, ...a, ...b, ...c, ...d]
console.timeEnd('spread-many');
我只回答性能问题,因为已经有关于场景的很好的答案。我编写了一个测试并在最新的浏览器上执行了它。下面是结果和代码。
/*
* Performance results.
* Browser Spread syntax concat method
* --------------------------------------------------
* Chrome 75 626.43ms 235.13ms
* Firefox 68 928.40ms 821.30ms
* Safari 12 165.44ms 152.04ms
* Edge 18 1784.72ms 703.41ms
* Opera 62 590.10ms 213.45ms
* --------------------------------------------------
*/
下面是我写的和使用的代码
const array1 = [];
const array2 = [];
const mergeCount = 50;
let spreadTime = 0;
let concatTime = 0;
// Used to popolate the arrays to merge with 10.000.000 elements.
for (let i = 0; i < 10000000; ++i) {
array1.push(i);
array2.push(i);
}
// The spread syntax performance test.
for (let i = 0; i < mergeCount; ++i) {
const startTime = performance.now();
const array3 = [ ...array1, ...array2 ];
spreadTime += performance.now() - startTime;
}
// The concat performance test.
for (let i = 0; i < mergeCount; ++i) {
const startTime = performance.now();
const array3 = array1.concat(array2);
concatTime += performance.now() - startTime;
}
console.log(spreadTime / mergeCount);
console.log(concatTime / mergeCount);
虽然有些回复在谈到大数组的性能时是正确的,但在处理小数组时性能却大不相同。
您可以在 https://jsperf.com/spread-vs-concat-size-agnostic 上自行查看结果。
如您所见,spread
对于较小的数组要快 50%,而 concat
对于大数组要快好几倍。
concat
和 push
之间有一个非常重要的区别,前者 不会 改变底层数组,要求您分配结果到相同或不同的数组:
let things = ['a', 'b', 'c'];
let moreThings = ['d', 'e'];
things.concat(moreThings);
console.log(things); // [ 'a', 'b', 'c' ]
things.push(...moreThings);
console.log(things); // [ 'a', 'b', 'c', 'd', 'e' ]
我已经看到由 concat
更改数组(为朋友交谈;)的假设引起的错误。
@georg 的回答有助于查看比较。我也很好奇 .flat() 在 运行 中如何比较,这是迄今为止最糟糕的。如果速度优先,请不要使用 .flat() 。 (直到现在我才意识到这一点)
let big = new Array(1e5).fill(99);
let i, x;
console.time("concat-big");
for (i = 0; i < 1e2; i++) x = [].concat(big);
console.timeEnd("concat-big");
console.time("spread-big");
for (i = 0; i < 1e2; i++) x = [...big];
console.timeEnd("spread-big");
console.time("flat-big");
for (i = 0; i < 1e2; i++) x = [[], big].flat();
console.timeEnd("flat-big");
let a = new Array(1e3).fill(99);
let b = new Array(1e3).fill(99);
let c = new Array(1e3).fill(99);
let d = new Array(1e3).fill(99);
console.time("concat-many");
for (i = 0; i < 1e2; i++) x = [1, 2, 3].concat(a, b, c, d);
console.timeEnd("concat-many");
console.time("spread-many");
for (i = 0; i < 1e2; i++) x = [1, 2, 3, ...a, ...b, ...c, ...d];
console.timeEnd("spread-many");
console.time("flat-many");
for (i = 0; i < 1e2; i++) x = [1, 2, 3, a, b, c, d].flat();
console.timeEnd("flat-many");