Разница между использованием синтаксиса распространения (...) и 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' ]
Существует ли разница между этими двумя методами с точки зрения производительности при работе с более крупными данными?
Ответы
Ответ 1
Оба 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)
Ответ 2
Помимо то, что указал ftor, 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
Ответ 3
Интерпретация вопроса, which is more performant in general, using.push() as an example
выглядит так, что apply [только немного] быстрее (за исключением 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)
Chrome 71.0.3578.80 (Official Build) (64-bit)
, FF 63.0.3 (64-bit)
и Edge 42.17134.1.0
, и это были мои результаты после того, как я несколько раз запускал их самостоятельно . Первоначальные результаты всегда были искажены так или иначе
![benchmark results for the three browsers]()
Как вы можете видеть, Edge, кажется, имеет лучшую реализацию для apply
чем для ...
(но не пытайтесь сравнивать результаты в разных браузерах, мы не можем сказать, имеет ли Edge лучшее apply
чем другие, хуже ...
, или немного оба из этих данных).
Учитывая это, если только вы не нацеливаетесь на Edge специально, я бы сказал, что давайте ...
же, как он читается чище, особенно если вам нужно передать объект обратно, чтобы apply
на this
.
Возможно, что он также зависит от размера массива, так как, как сказал @Jaromanda X
собственное тестирование и измените 200
если вам действительно нужно убедиться.
Другие ответы интерпретировали вопрос так, как which would be better for.push() specifically
, и which would be better for.push() specifically
"проблемой", просто порекомендовав just use.concat()
, что по сути является стандартом, why are you doing it that way?
что может раздражать некоторых людей из Google, которые не ищут решений для .push()
(скажем, Math.max
или вашей собственной пользовательской функции).
Ответ 4
Для добавления к большому массиву оператор распространения значительно быстрее. Я не знаю, как @ftor
/@Liau Jian Jie
сделал свои выводы, возможно, плохие тесты.
Chrome 71.0.3578.80 (Official Build) (64-bit)
FF 63.0.3 (64-bit)
Chrome 71.0.3578.80 (Official Build) (64-bit)
, FF 63.0.3 (64-bit)
Edge 42.17134.1.0
FF 63.0.3 (64-bit)
и Edge 42.17134.1.0
Это имеет смысл, поскольку concat()
создает копию массива и даже не пытается использовать одну и ту же память.
Дело в "мутациях", похоже, не основано ни на чем; если вы перезаписываете свой старый массив, concat()
имеет никаких преимуществ.
Единственная причина не использовать ...
это переполнение стека, я согласен с другими ответами, которые вы не можете использовать ...
или apply
.
Но даже тогда использование for {push()}
более или менее в два раза быстрее, чем concat()
во всех браузерах, и не приводит к переполнению.
Нет никакой причины использовать concat()
если вам не нужно сохранить старый массив.
Ответ 5
С помощью 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/
Ответ 6
если вы используете ES2015, то оператор распространения - это путь. Используя оператора распространения, ваш код выглядит менее подробным и намного более чистым по сравнению с другим подходом. Когда дело доходит до скорости, я считаю, что будет мало выбора между обоими подходами.