代入にも罠があった
浅いコピーと深いコピー
関数の引数だとか、変数への代入だとかは頻繁にやるけど、実は2種類に分けることができる。
それは、値を入れるか、参照を入れるかの二つだ。まあ例を見ればわかる
const a = 10;
const b = a;
const b = 20;
console.log(a,b)
//10,20
const a = {text:"hello"}
const b = a;
b.text = "how are you?"
console.log(a,b)
//how are you? how are you?
これらの条件だが、オブジェクトかプリミティブかによって分岐する。
値がオブジェクトな変数を、ほかの変数に代入すると、それは参照渡しの状態になる。
値がプリミティブ型なら、それは値渡しになる。
ちなみに、オブジェクト変数を代入し、参照渡しになっても、ほかのオブジェクトを代入すれば、それは解除される。
let a = {a:1,};
let b = a;
b = {a:2};
console.log(a.a,b.a);
//1,2
オブジェクトを値渡しでコピーするには、浅いコピーと深いコピーの2通りのやり方がある。
浅いコピー
const a = {
shallow:1,
nest:{c:2}
}
const b = {...a}
b.shallow=10
b.nest.c=3;
console.log(a.shallow,b.shallow);
//1,10 独立してる
console.log(a.nest.c,b.nest.c);
//3,3 参照が渡されてて共有
深いコピー(めんどいので、多用するならライブラリとか使おう)
const a = {
shallow:1,
nest:{c:2}
}
const b = JSON.parse(JSON.stringify(a));
b.shallow=10
b.nest.c=3;
console.log(a.shallow,b.shallow);
//1,10 独立してる
console.log(a.nest.c,b.nest.c);
//2,3 ネストされた部分も独立
ただし、深いコピーは容量を食うので、必要な場合のみに使用
追記(実は参照渡しじゃない)
参照渡しについて誤認していたので追記します。
まず参照についてきちんと確認しておきましょう。
参照:参照するデータのメモリ上のアドレス
参照渡し=元の変数そのものを引っ張ってくるということです。
そして、通常TypeScriptではそのようなことは起こらず、参照を変数に格納しているだけという状態になります。
わかりずらいので、例を挙げてみましょう。
まずは、通常typescriptで参照がどのように動くのか追ってみましょう。
a={num:1};//{num:1}がadr1というアドレスに格納され、adr1というアドレスがaに格納されます。aのアドレスはadr2としましょうか。
b=a;//bにadr1というアドレスが格納されます。bのアドレスはadr3としましょう。
a,bはそれぞれ異なるアドレスに存在しているため、同じ変数ではありません。→元の変数そのものを持ってきているわけではない。今回の場合は、bはaそのものではない。同じ値(アドレス)を持った別の変数に過ぎないわけです。つまり、参照渡しではない。ただし、参照先が同じなので、adr1の値が変わればa,bどちらにも反映される。
参照渡しの例
#include <iostream>
using namespace std;
void update(int& x) {
x = 10; // x は元の変数そのもの(参照渡し)
}
int main() {
int a = 5;
update(a); // a を参照渡し
cout << "a = " << a << endl; // `a=${a}\n`を出力するのと同等の処理
//出力はa=10
return 0;
}
関数内で扱っているxはaそのもの(同じアドレスの変数を別の名前で扱っているだけ)。なので、変更が反映される。
Discussion
インスタンスが共有されているだけなので、それは参照渡しではないのではないでしょうか?
※参照渡しが共有するのは変数であって オブジェクトではないです。
もしも参照渡しの様な変数の参照を渡す挙動を確認したい場合は import した変数を export 元で 代入するとその動作を確認することができます。
structuredClone おすすめします。
指摘があった通り、参照渡しというのは正しくなかったようです。
修正しました。