仕事でnode書いている時にオブジェクトの参照渡しで思わぬ挙動が起きることがよくありました。
参照渡しと値渡しの違いについては下の記事を参考にしてください。
今回は、オブジェクトを配列で持つ変数の注意点です。
配列を代入ではなくコピーする
配列を複製してオリジナルとコピーした変数を使いたくなる場面が少なからずあると思います。
そんな時によくやってしまいがちなのが参照渡しです。
この場合、変数bの要素だけを変更したとしても、変数aまで変わってしまいます。
const a = [1, 2, 3];
const b = a;
console.log(a); // 1, 2, 3
console.log(b); // 1, 2, 3
b[0] = 4;
console.log(a); // 4, 2, 3
console.log(b); // 4, 2, 3
aまで変わったらオリジナルじゃないじゃん!って嘆くことってありますよね。(自分はよくやらかしてました)
よく解決策として上げられるのがconcatという関数です。
他にも方法があると思いますが、筆者はこれをよく使うので今回はconcatにだけ着目します。
concat関数は元の配列に対して引数の要素を付け加える関数ですが、新しく配列を生成するのでオリジナルの変数とは参照先が異なる値を持つことになります。
そのため、コピー先の要素を変更してもオリジナルの変数が変更されることはありません。
const a = [1, 2, 3];
const b = a.concat();
console.log(a); // 1, 2, 3
console.log(b); // 1, 2, 3
b[0] = 4;
console.log(a); // 1, 2, 3
console.log(b); // 4, 2, 3
これが配列の代入ではなくコピーです。
オブジェクトの配列をconcatしたらどうなるか
表形式でデータを表示する場合など、オブジェクトを配列の形式で持つことがあると思います。
const a = [{"key1" : "value1"}, {"key2" : "value2"}]
それではいつものようにこいつをconcatでコピーして中のオブジェクトを更新してみましょう。
const a = [{"key1" : "value1"}, {"key2" : "value2"}];
const b = a.concat();
console.log(a); // [ { key1: 'value1' }, { key2: 'value2' } ]
console.log(b); // [ { key1: 'value1' }, { key2: 'value2' } ]
b[0]["key1"] = "value3";
console.log(a); // [ { key1: 'value3' }, { key2: 'value2' } ]
console.log(b); // [ { key1: 'value3' }, { key2: 'value2' } ]
あれ?変数aも変わってるやん・・・コピーしたはずなのに・・・
なんてことがあったので解決策調べてまとめて、今回の記事を作成しました(前置きが長い)
中のオブジェクトもコピーしよう
concatで配列をコピーしたところで、中のオブジェクトのキーの参照先はオリジナルとコピーで同じものになる、つまり更新すれば片方も変わることになります。
それなら中のオブジェクトもコピーしてしまおう!という発想で解決しました。
const a = [{"key1" : "value1"}, {"key2" : "value2"}];
const b = a.map((obj) => Object.assign({}, obj));
console.log(a); // [ { key1: 'value1' }, { key2: 'value2' } ]
console.log(b); // [ { key1: 'value1' }, { key2: 'value2' } ]
b[0]["key1"] = "value3";
console.log(a); // [ { key1: 'value1' }, { key2: 'value2' } ]
console.log(b); // [ { key1: 'value3' }, { key2: 'value2' } ]
assign関数はオブジェクトをマージして新しくオブジェクトを作ってくれる関数です。
こんな面倒なことしなくてもいい感じに配列コピーしてくれよ、って思いますがプログラムは思い通りにいかないもんですね。
しかもこれ、オブジェクトの中が配列やオブジェクトになるとさらにconcatやらassignやらやらないといけなくなることに後々気づきました。
おわりに
配列やオブジェクトをコピーして使う時には、参照渡しや値渡しを意識しながら使う必要があります。
Javascriptに限らず様々な言語でも同じ事象は発生するので、今回のような事象を機にプログラミング言語を詳しく知っていくことも大事かなと思います。
ちなみに記事を書く過程で色々調べていたら、今回のように入れ子になった配列やオブジェクトをコピーすることをディープコピーということが分かりました。
しかもディープコピー用のライブラリとかもあるらしいので、先人たちって偉大だなと痛感しました。(というか早く知りたかった!)
[JavaScript]色々なディープコピー
初投稿につき稚拙なところはご容赦ください。
最後まで閲覧いただきありがとうございました。