В чем разница между spread operator
и array.concat()
let parts = ['four', 'five'];
let numbers = ['one', 'two', 'three'];
console.log([...numbers, ...parts]);
В чем разница между spread operator
и array.concat()
let parts = ['four', 'five'];
let numbers = ['one', 'two', 'three'];
console.log([...numbers, ...parts]);
Well console.log(['one', 'two', 'three', 'four', 'five'])
имеет такой же результат, поэтому зачем использовать здесь? :П
В общем случае вы использовали бы concat
когда у вас есть два (или более) массива из произвольных источников, и вы бы использовали синтаксис распространения в литерале массива, если раньше были известны дополнительные элементы, которые всегда являются частью массива. Поэтому, если у вас будет литерал массива с concat
в вашем коде, просто используйте синтаксис распространения и просто используйте concat
иначе:
[...a, ...b] // bad :-(
a.concat(b) // good :-)
[x, y].concat(a) // bad :-(
[x, y, ...a] // good :-)
Кроме того, две альтернативы ведут себя по-разному, когда имеют дело с значениями, отличными от массива.
Как сказал @Bergi, 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
не пытается перебрать генератор и добавляет его как единое целое, в то время как ...
приятно выбирает все значения из него.
Подводя итог, если ваши аргументы, возможно, не являются массивами, выбор между concat
и ...
зависит от того, хотите ли вы, чтобы они были повторены.
Выше описано поведение по умолчанию concat
, однако ES6 предоставляет способ переопределить его с помощью 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' ]
С точки зрения производительности concat
быстрее, возможно, потому, что он может выиграть от оптимизации под конкретные массивы, в то время как ...
должен соответствовать общему протоколу итерации. Тайминги:
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');
Единственное различие, которое я считаю правдивым, заключается в том, что использование оператора с расширенными размерами большого массива даст вам 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)
Я отвечаю только на вопрос производительности, так как уже есть хорошие ответы относительно сценариев. Я написал тест и выполнил его в самых последних браузерах. Ниже приведены результаты и код.
/*
* 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://www.malgol.com/how-to-merge-two-arrays-in-javascript/.
Я думаю, что спрэд оператора выполняется быстрее, чем concat. Пожалуйста, перейдите по этой ссылке и просмотрите изображение ниже.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax