Разница между использованием синтаксиса распространения (...) и 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, то оператор спреда - это то, что нужно. Используя оператор распространения, ваш код выглядит менее многословным и намного чище по сравнению с другим подходом. Когда дело доходит до скорости, я полагаю, что будет мало выбора между обоими подходами.

Другие вопросы по тегам