web-dev-qa-db-ja.com

配列を処理する場合のスプレッド構文(...)とPush.applyの使用の違い

2つのアレイがあります。

const pets = ["dog", "cat", "hamster"]

const wishlist = ["bird", "snake"]

wishlistpetsに追加します。これは、2つの方法を使用して実行できます。

方法1:

pets.Push.apply(pets,wishlist)

その結果:[ 'dog', 'cat', 'hamster', 'bird', 'snake' ]

方法2:

pets.Push(...wishlist)

その結果も:[ 'dog', 'cat', 'hamster', 'bird', 'snake' ]

より大きなデータを処理する場合、パフォーマンスの点でこれら2つの方法に違いはありますか?

22
Net Myth

どちらも 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には変異を回避するという利点もあります。突然変異は微妙な副作用を引き起こす可能性があるため、有害と見なされます。

しかし、それは教義ではありません。関数のスコープを調べ、ミューテーションを実行してパフォーマンスを向上させ、ガベージコレクションを軽減する場合は、親スコープに表示されていない限り、ミューテーションを実行できます。

20
user6445533

質問をwhich is more performant in general, using .Push() as an exampleと解釈すると、applyは[ほんの少し]速くなります(MS Edgeを除く、以下を参照)。

ここにパフォーマンステストがあります 2つのメソッドに対して関数を動的に呼び出すオーバーヘッドのみ。

_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_でテストしましたが、これらは自分で数回実行した後の私の結果です 最初の結果は常に何らかの方法で歪んでいました

benchmark results for the three browsers

ご覧のように、Edgeはapplyの実装がitよりも_..._よりも優れているようです(ただし、結果を比較しないでください)ブラウザでは、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?_であり、 .Push()(たとえば、_Math.max_、または独自のカスタム関数)を使用して解決策を探していない、グーグルから来た人々を怒らせます。

2
Hashbrown

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

2
Liau Jian Jie

Pushでは既存の配列に追加し、spreadオペレーターではコピーを作成します。

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

特に大きなアレイの場合は、参照することをお勧めします。

スプレッドオペレーターは、従来は次のような方法で行われるコピーを行う高速な方法です。

a=[1,2,3]
b=[]
a.forEach(i=>b.Push(i))
a.Push(4)
alert(b);

=> 1、2、3

コピーが必要な場合は、spreadオペレーターを使用してください。これは高速です。または、@ ftorが指摘するconcatを使用します。そうでない場合は、プッシュを使用します。ただし、変更できないコンテキストがあることに注意してください。さらに、これらの関数のいずれかを使用すると、深いコピーではなく、浅いコピーが得られます。ディープコピーにはlodashが必要です。詳細はこちら: https://slemgrim.com/mutate-or-not-to-mutate/

0
Rik

答えはuser6445533の回答を受け付けましたが、テストケースはちょっと変だと思います。それは、通常、spread演算子をどのように使用すべきかとは思えません。

なぜこんなことができないのですか?

let newPets = [...pets, ...wishlist]

説明されているように、スタックオーバーフローの問題には直面しません。 Hashbrownが述べたように、それは同様にあなたにパフォーマンス上の利益を与えるかもしれません。

*私もES6の学習の最中です。私が間違っているとすみません。

0
Neo

大きな配列に追加する場合、 spread演算子はvastly高速です です。 _@ftor_/_@Liau Jian Jie_がどのようにして結論を​​導き出したのかわかりません。

test results 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()すべてのブラウザでオーバーフローしません
あなたneedtoでない限り、concat()を使用する理由はありません古い配列を保持

0
Hashbrown