Разница между использованием синтаксиса распространения (...) и push.apply при работе с массивами
У меня есть два массива,
const pets = ["dog", "cat", "hamster"]
const wishlist = ["bird", "snake"]
Я хочу добавить wishlist
в pets
, что можно сделать с помощью двух методов,
Способ 1:
pets.push.apply(pets,wishlist)
Что приводит к: [ 'dog', 'cat', 'hamster', 'bird', 'snake' ]
Способ 2:
pets.push(...wishlist)
Что также приводит к: [ 'dog', 'cat', 'hamster', 'bird', 'snake' ]
Есть ли разница между этими двумя методами с точки зрения производительности, когда я работаю с большими данными?
3 ответа
И то и другое Function.prototype.apply
и синтаксис распространения может вызвать переполнение стека при применении к большим массивам:
let xs = new Array(500000),
ys = [], zs;
xs.fill("foo");
try {
ys.push.apply(ys, xs);
} catch (e) {
console.log("apply:", e.message)
}
try {
ys.push(...xs);
} catch (e) {
console.log("spread:", e.message)
}
zs = ys.concat(xs);
console.log("concat:", zs.length)
использование Array.prototype.concat
вместо. Помимо избежания переполнения стека concat
имеет преимущество в том, что он также избегает мутаций. Мутации считаются вредными, потому что они могут привести к незначительным побочным эффектам.
Но это не догма. Если вы работаете с областью действия и выполняете мутации для повышения производительности и облегчения сборки мусора, вы можете выполнять мутации, если они не видны в родительской области.
С помощью push вы добавляете к существующему массиву, с помощью оператора распространения вы создаете копию.
a=[1,2,3]
b=a
a=[...a, 4]
alert(b);
=> 1, 2, 3
a=[1,2,3]
b=a
a.push(4)
alert(b);
=> 1, 2, 3, 4
push.apply также:
a=[1,2,3]
c=[4]
b=a
Array.prototype.push.apply(a,c)
alert(b);
=> 1, 2, 3, 4
Конкат это копия
a=[1,2,3]
c=[4]
b=a
a=a.concat(c)
alert(b);
=> 1, 2, 3
Ссылка предпочтительнее, особенно для больших массивов.
Оператор Spread - это быстрый способ сделать копию, которая обычно выполняется с помощью чего-то вроде:
a=[1,2,3]
b=[]
a.forEach(i=>b.push(i))
a.push(4)
alert(b);
=> 1, 2, 3
Если вам нужна копия, используйте оператор распространения, это быстро для этого. Или используйте concat, как указано @ftor. Если нет, используйте push. Имейте в виду, однако, что есть некоторые контексты, в которых вы не можете мутировать. Кроме того, с любой из этих функций вы получите мелкую копию, а не глубокую копию. Для глубокого копирования вам понадобится lodash. Узнайте больше здесь: https://slemgrim.com/mutate-or-not-to-mutate/
Для добавления к большому массиву оператор распространения значительно быстрее. Я не знаю как @ftor
/ @Liau Jian Jie
сделали свои выводы, возможно, плохие тесты.
Chrome 71.0.3578.80 (Official Build) (64-bit)
, FF 63.0.3 (64-bit)
& Edge 42.17134.1.0
Это имеет смысл, так как concat()
делает копию массива и даже не пытается использовать ту же память.
Кажется, что вещь о "мутациях" не основана ни на чем; если вы перезаписываете свой старый массив, concat()
не имеет никаких преимуществ.
Единственная причина не использовать ...
будет переполнение стека, я согласен с другими ответами, которые вы не можете использовать ...
или же apply
,
Но даже тогда просто используя for {push()}
более или менее в два раза быстрее, чем concat()
во всех браузерах и не переполнится.
Там нет причин для использования concat()
если вам не нужно сохранить старый массив.
Помимо того, на что указал Фтор, Array.prototype.concat
в среднем как минимум в 1,4 раза быстрее, чем оператор распределения массива.
Смотрите результаты здесь: https://jsperf.com/es6-add-element-to-create-new-array-concat-vs-spread-op
Вы можете запустить тест в своем собственном браузере и на машине здесь: https://www.measurethat.net/Benchmarks/Show/579/1/arrayprototypeconcat-vs-spread-operator
Ответ user6445533 был принят как ответ, но я считаю, что тестовый пример немного странный. Это не похоже на то, как вы обычно должны использовать оператор распространения.
Почему просто не нравится:
let newPets = [...pets, ...wishlist]
Он не столкнется с проблемой переполнения стека, как описано. Как упоминал Хэшбраун, это также может дать вам преимущество в производительности.
* Я также изучаю ES6. Простите, если ошибаюсь.
Интерпретировать вопрос как which is more performant in general, using .push() as an example
Похоже, что применить это [только немного] быстрее (за исключением MS Edge, см. ниже).
Вот тест производительности только на накладные расходы при динамическом вызове функции для двух методов.
function test() { console.log(arguments[arguments.length - 1]); }
var using = (new Array(200)).fill(null).map((e, i) => (i));
test(...using);
test.apply(null, using)
Я проверял в Chrome 71.0.3578.80 (Official Build) (64-bit)
, FF 63.0.3 (64-bit)
& Edge 42.17134.1.0
и это были мои результаты после того, как я их несколько раз запускал . Первоначальные результаты всегда были искажены так или иначе
Как вы можете видеть, Edge, кажется, имеет лучшую реализацию для apply
чем это делает для ...
(но не пытайтесь сравнивать результаты в разных браузерах, мы не можем сказать, есть ли у Edge лучший apply
чем другие, хуже ...
или немного из этих данных).
Учитывая это, если вы не нацелены на Edge специально, я бы сказал, пойти с ...
так же, как он читается чище, особенно если вам нужно передать объект обратно в apply
за this
,
Возможно, это зависит от размера массива, так что @Jaromanda X
сказал, сделать свое собственное тестирование и изменить 200
если вам нужно действительно убедиться.
Другие ответы интерпретировали вопрос как which would be better for .push() specifically
и поймать решаемую "проблему", просто порекомендовав just use .concat()
что в основном стандарт why are you doing it that way?
что может раздражать некоторых людей из Google, которые не ищут решений, связанных с .push()
(сказать, Math.max
или ваша собственная функция).
Если вы используете ES2015, то оператор спреда - это то, что нужно. Используя оператор распространения, ваш код выглядит менее многословным и намного чище по сравнению с другим подходом. Когда дело доходит до скорости, я полагаю, что будет мало выбора между обоими подходами.