6/5/2022
JavaScriptで配列をdeep copyする方法
普段JavaScriptで配列をdeep copyする際は .slice()
を使っているのですが、先日チームの人から .map()
でもいいのではないかと言われたのであらためて調べてみました
deep copyとは
オブジェクトや配列をコピーする際には浅いコピー(shallow copy)と深いコピー(deep copy)があります。
Pythonのドキュメントには
浅いコピー(shallow copy)は新たな複合オブジェクトを作成し、その後(可能な限り)元のオブジェクト中に見つかったオブジェクトに対する参照を挿入します。
深いコピー(deep copy)は新たな複合オブジェクトを作成し、その後元のオブジェクト中に見つかったオブジェクトのコピーを挿入します。
と説明されています
つまりdeep copyでは元の値と同値のものを挿入するのに対し、shallow copyでは元の値の参照を挿入するという違いがあります
そのため、shallow copyでは元の値が変更されればコピー先のものも変更されます
deep copyはなぜ必要なのか
shallow copyでは元の値が変更されればコピー先のものも変更されてしまうと都合が悪いときがあります
たとえばvuexを使っているときに、mutationを介さずに直接値を変更しようと怒られます。そこで、一度値を複製し、変更を加えたものをvuexに再代入するという方法を使いたいときがあります。
deep copyの比較
本題のdeep copyのやり方の比較です
こちらの冒頭に書いてある通り、巷では slice
がまずよくやる方法で、このエントリでも 'traditional methods'という表現をされています
これはES6以前の話で、ES6以降はスプレッド演算子でコピーするのがよくおすすめされていました
次に、速度で言うとmapよりもsliceの方が速くて、さらにsliceよりもconcatの方がわずかに速いそうです。似た方法にfilterもありますが、意味合いを考えるとmap, filter, concatよりもsliceを使うべきだと思います
ただ、罠としてこれらの方法はdeep copyに見えて実はshallow copyで、1次元配列だとdeepに見えるのですが、jsではarrayはobject referencesなので、2次元配列だとshallowであることがわかります本当にdeep copyをしたければ一度JSON化するか、大人しくlodashを使えとのことでした
さいごに
lodashを使うデメリットとしてはバンドルサイズがとんでもないという問題があって、それを解決したlodash-esというものを弊社でも使っていますが、今回は一次元配列だったので、従来通りsliceかスプレッド演算子を使うのが丸いという結論になりました