JavaScript

【Javascript】Arrayをconcatを使って値渡しできたと思ったら中のObjectは参照渡しのままだった件について

Arrayオブジェクトの中にObjectがあった際、Array.concat()だけすれば中のObjectも別のインスタンスにできると勘違いしていて詰まったので忘れないために記事にします。

結論から言ってしまうと解決するためにはArray.concat()してからObject.assign()すればよい


まずArrayオブジェクトとObjectの代入について


Arrayオブジェクト

まずArrayオブジェクトは下のコードのようにただ変数にリストを代入しても参照が渡されるだけで新しいリストが作られるわけではない。


参照渡しの例

const list1 = ["first"];

const list2 = list1;
list1.push("second");
console.log(list1); //["first","second"]
console.log(list2); //["first","second"]

そこで値渡しをしたい場合は、下のコードのようにArray.concat()を使えばリストをコピーして新しいリストとして生成できる。


値渡しの例

const list1 = ["first"];

const list2 = list1.concat();
list1.push("second");
console.log(list1); //["first","second"]
console.log(list2); //["first"]


Object

そして、下のコードを見れば分かるようにObjectもArrayオブジェクトと同じでそのまま変数に代入しても参照が渡されるだけ


参照渡しの例

const obj1 = {"key1": "value1"}

const obj2 = obj1;
obj1["key2"] = "value2";
console.log(obj1); //{"key1":"value1","key2":"value2"}
console.log(obj2); //{"key1":"value1","key2":"value2"}

なので値渡しをしたい場合は、下のコードのようにObject.assign()を使って新しいインスタンスにする必要がある


値渡しの例

const obj1 = {"key1": "value1"}

const obj2 = Object.assign({},obj1);
obj1["key2"] = "value2";
console.log(obj1); //{"key1":"value1","key2":"value2"}
console.log(obj2); //{"key1":"value1"}


本題

今回問題だったのは下のコードのようなArrayオブジェクトの中にObjectが入っていた場合



const list 1 = [{"key1":"value1"}];


このようなリストの場合、Array.concat()で新しいリストにした場合でもリスト自体は値渡しできているがリストの中のObjectは参照を保っているという状態になる。


中のObjectは参照を保っている

const list1 =  [{"key1":"value1"}];

const list2 = list1.concat();
list1[0]["key2"] = "value2";
list1.push("second");
console.log(list1); //[{"key1":"value1","key2":"value2"},"second"]
console.log(list2); //[{"key1":"value1","key2":"value2"}]

解決策としては下のコードのようにリストに対してArray.concat()をしてそのコピーしたリストの中のObjectに対して更にObject.assign()をする。


解決策

const list1 =  [{"key1":"value1"}];

const list2 = list1.concat();
list2[0] = Object.assign({},list2[0]);
list1[0]["key2"] = "value2";
list1.push("second");
console.log(list1); //[{"key1":"value1","key2":"value2"},"second"]
console.log(list2); //[{"key1":"value1"}]

これでObjectが含まれているリストでも完全に別のリストとして扱えるようになる。

おまけとして付け加えると

もし下のコードのようにリストの中に複数のObjectがあるならArray.map()等のループ系のメソッドを使ってやるときれいに処理できる気がする。


おまけ

const list1 =  [{"key1":"value1"},{"key2":"value2"},{"key3":"value3"}];

let list2 = list1.concat();
list2 = list2.map((obj)=>{
return Object.assign({},obj);
});
list1[0]["key4"] = "value4";
console.log(list1); //[{"key1":"value1","key4":"value4"},{"key2":"value2"},{"key3":"value3"}]
console.log(list2); // [{"key1":"value1"},{"key2":"value2"},{"key3":"value3"}]


メソッドの解説リンク

Array.concat() --MDN

Array.map() --MDN

Object.assign() --MDN


以上です。

もし指摘等がありましたらコメントにてお願いします。