2022.06.06

JavaScriptで配列をdeep copyする方法

普段JavaScriptで配列をdeep copyする際は .slice() を使っているのですが、先日チームの人から .map() でもいいのではないかと言われたのであらためて調べてみました

deep copyとは

オブジェクトや配列をコピーする際には浅いコピー(shallow copy)と深いコピー(deep copy)があります。<br> Pythonのドキュメントには

浅いコピー(shallow copy)は新たな複合オブジェクトを作成し、その後(可能な限り)元のオブジェクト中に見つかったオブジェクトに対する参照を挿入します。 深いコピー(deep copy)は新たな複合オブジェクトを作成し、その後元のオブジェクト中に見つかったオブジェクトのコピーを挿入します。

と説明されています<br> つまりdeep copyでは元の値と同値のものを挿入するのに対し、shallow copyでは元の値の参照を挿入するという違いがあります<br> そのため、shallow copyでは元の値が変更されればコピー先のものも変更されます

deep copyはなぜ必要なのか

shallow copyでは元の値が変更されればコピー先のものも変更されてしまうと都合が悪いときがあります<br> たとえばvuexを使っているときに、mutationを介さずに直接値を変更しようと怒られます。そこで、一度値を複製し、変更を加えたものをvuexに再代入するという方法を使いたいときがあります。

deep copyの比較

本題のdeep copyのやり方の比較です<br> こちらの冒頭に書いてある通り、巷では slice がまずよくやる方法で、このエントリでも 'traditional methods'という表現をされています<br> これはES6以前の話で、ES6以降はスプレッド演算子でコピーするのがよくおすすめされていました<br> 次に、速度で言うとmapよりもsliceの方が速くて、さらにsliceよりもconcatの方がわずかに速いそうです。似た方法にfilterもありますが、意味合いを考えるとmap, filter, concatよりもsliceを使うべきだと思います<br> ただ、罠としてこれらの方法はdeep copyに見えて実はshallow copyで、1次元配列だとdeepに見えるのですが、jsではarrayはobject referencesなので、2次元配列だとshallowであることがわかります本当にdeep copyをしたければ一度JSON化するか、大人しくlodashを使えとのことでした<br>

さいごに

lodashを使うデメリットとしてはバンドルサイズがとんでもないという問題があって、それを解決したlodash-esというものを弊社でも使っていますが、今回は一次元配列だったので、従来通りsliceかスプレッド演算子を使うのが丸いという結論になりました