はじめに
本記事は Udemy著者『 CodeMafia 』様の「【JS】ガチで学びたい人のためのJavaScriptメカニズム」 の内容を参考にしています。
JavaScriptの基礎的な文法から応用的な使用例まで備忘録・復習を兼ねて記事にしていこうと思います。
特にUdemy 「【JS】ガチで学びたい人のためのJavaScriptメカニズム」 の質問内容に目を通しています。Jsの学習を共に解決していけたら幸いです。
その他のQuiitaの記事やMDNの構文は積極的に引用しようと思います。
対象読者様
- Js入門者の方
- Jsの参照とコピーがこんがらがって自信がない方
- 関数の引数がうまく操れない方
- 引数がうまく操れない方
- Javascriptの参照の基本的な特訓をしたい方
- ...etc
ウォーミングアップ(プリミティブ値)
四角い箱はメモリ空間x番地のようなものです。
1. 変数aは'fugaa'という 値 への 参照を保持している。
2. 変数aが参照している値が別のメモリ空間に コピー される。変数bから参照が貼り直される。
3. bに 再代入 をした場合はbの参照先が'fugaa'から'hogebb'に変更される。
変数aが参照している'fugaa'という値自体が コピー されているため(aそのものがコピーされている訳ではない)、bの値を変更してもaの値は保持されている。
次の例で示すオブジェクトではコピーの挙動は異なってきます
なんのことだか分かりにくいかもしれないので、次へ行きます。
ウォーミングアップ(オブジェクト)
1. cからオブジェクト{}への参照が貼られ、実体({prop: OO})が別の空間に格納される。これが'propCCC'への参照を保持している状態
2. dという変数にcを格納すると、 {}が別のメモリ空間にコピーされる。この{}の参照が保持しているのは、オブジェクトの実体であるため、この時点では d = {prop:CCC}、
3. d.prop = 'propDDD'とすると、元々cが{}を参照し、オブジェクトの実体を参照しているので、'propDDD'に 参照を貼り直す ことになる。
オブジェクトを保持している変数 を他の変数に代入した場合には、propertyを変更すると、コピー元のオブジェクトにも影響を与える。
ウォーミングアップ(オブジェクトの再代入)
1. cからオブジェクト{}への参照が貼られ、実体({prop: XX})が別の空間に格納される。これが'propCCC'への参照を保持している状態
2. dという変数にcを格納すると、 {}が別のメモリ空間にコピーされる。この{}の参照が保持しているのは、オブジェクトの実体であるため、この時点では d = {prop:CCC}、 ここまでは上記と同じ 。
3. d = {}として、dに別のオブジェクトを代入すると 参照が貼り直される ため、cには影響を与えない。
{ }への参照を切り離すか、元のオブジェクトに影響を与えるようにするか時と場合により判断するということです。次の 分割代入 の箇所で少し深掘りします。
ウォーミングアップ(分割代入)
分割代入:オブジェクトから特定のプロパティーを抽出して宣言を行う。
1. 上述と同じなので割愛。
2. 分割代入で"prop"という新しい変数を宣言した場合、"prop"というプロパティが参照している'propA'がコピーされ、propという新しい変数がそれを参照するようになる。オブジェクトとは切り離された状況でpropという変数が宣言されている。
3. 変数"prop"に新しい値を代入すると参照が貼り直される。
実際は関数と絡めて使うことが多いので詳しくは後に回します。
関数や引数が関連してきた場合
例1:プリミティブ値aが実引数
let a = 0;
function f(arg) {
arg = 1;
console.log(a,arg); // a 0, arg 1
}
f(a); //実行
この関数はプリミティブ型の変数aを引数として実行しています。
aという変数は a = 0、というメモリ空間に保存されています。
今回引数として関数に渡してあげた場合のaはとりあえずは0です。
関数内で{arg = 1}とした場合は。
let arg = a;
arg = 1;
と記述した場合と同様になります。
従って、aはレキシカルスコープをたどって「let a = 0;」を参照し、「0」をargは「1」を出力します。
引数がプリミティブ値の場合、互いに影響を受けない
例2:オブジェクトbが実引数
let b = {
prop:0
}
function f(arg) {
arg.prop = 1;
console.log(b,arg); //{prop: 1} {prop: 1}
}
f(b);
オブジェクトbの値も変更されます。
この現象を 日本語的 に段階的に言い直すならば、
1. 「引数に渡したbというオブジェクトの参照がargにコピーされます。」
2. 「argというオブジェクはbと同じ オブジェクトの実体への参照 を保持しています。」
3. 「そのため、argの値を変更するとbにも影響が出ます」。
例3:分割代入を絡めた場合
オブジェクトを実引数。オブジェクトのプロパティを仮引数に分割代入して値の変化を観察します。
let obj = {
split: 0
}
function f(fugaobj) {//fugaobjにはobjが渡る。
let { split } = fugaobj; //objのsplitというプロパティを抽出。
split = 1; //新たな変数splitに1を代入。
console.log(obj, split) //=> {split: 0} 1
}
f(obj);
//簡単な書き方があるので以下も同じです。1行消せるのとよく使われる書き方です。
function f({split}) { //この段階でsplitのみ抽出できる
split = 1;
console.log(obj, split)//=> {split: 0} 1
}
f(obj);
オブジェクトの中身を抽出して、その値を変更してもオブジェクト自体には影響を及ぼしてないのが確認できました。
さて、次にもう少しオブジェクトの階層が深くなった場合について考えてみます。
//split1は参照を持っています(split1:{}の部分)
let obj = {
split1: {
split2: 0
}
}
let { split1 } = obj;//ここもsplit1は参照を持っています
split1.split2 = 1; //参照を持ったsplit1のプロパティを変更すると
console.log(obj, split1); //=> obj: {split1:{split:2 : 1}}
///元のobjも影響を受けることになります。
//一応2段階で分割代入すれば結果は
let obj = {
split1: {
split2: 0
}
}
let { split1 } = obj;
let { split2 } = split1.split2;
split1 = 1
split2 = 2;
console.log(obj, split1, split2);
//objはそのまま、split1 = 1, split2 = 2;
//となります。頭の体操みたいな感じですね。
まとめ
今回は変数の「参照とコピー」について、自分なりにまとめてみました。
これは一番シンプルな例だと思いますが、 自分自身 日本語がこんがらがります。
次回はもう少し複雑な参照に関する課題について問題を通して学びたいと思います。
間違っている箇所をご指摘いただけると喜びます(笑)
ありがとうございました