#JS学習シリーズの目的
このシリーズは、私ジャックが学んだJavaScriptのメカニズムについてアウトプットも兼ねて、
皆さんと知識や理解を共有するためのものです。
(理解に間違いがあればご指摘いただけると幸いです)
#前置き
今回は、前回の記事で書いた内容を踏まえて解説していきます!
余裕がある方は前回の記事➡【JS学習その④】プリミティブ型とオブジェクト ~データ型~をご覧ください
#参照とコピー
プリミティブ値とオブジェクトの参照とコピーについて解説します
##プリミティブ値のコピー
let a = 'Hello';
let b = a;
b = 'Bye';
console.log(a); /*Hello*/
console.log(b); /*Bye*/
上記のコードでは、
1.変数aは'Hello'という文字列への参照先を保持している
2.変数bに変数aを代入した時に、'Hello'が別のメモリ空間にコピーされ、変数bはそのコピーされた'Hello'への参照先を保持する
3.変数bに新たに'Bye'という文字列を代入した時に、変数bの参照先が'Hello'から'Bye'に変更される
※この時、変数aと変数bの参照先は、それぞれ独立しているため、変数bの参照先が変わっても変数aの参照先は変わらない
↑の流れになっています。
したがって、console.log()で確認した場合、コメントの内容になります。
このように、プリミティブ型の値を格納する変数を他の変数に渡した場合は、それぞれの値はそれぞれ独立して存在しているためどちらかの値を変更してももう一方の値が変更されることはありません。
##オブジェクトのコピー
let a = {
prop: 'Hello'
};
let b = a;
b.prop = 'Bye';
console.log(a);
// {
// prop: 'Bye'
// };
console.log(b);
// {
// prop: 'Bye'
// };
上記のコードでは、
1.変数aに{...}(オブジェクト)への参照先が保持される
2.1.は{prop}への参照先を保持している
3.2.のpropは'Hello'という文字列への参照先を保持している
4.変数bに1.の{...}(オブジェクト)への参照先がコピーされる
※この時、変数aと変数bの{...}(オブジェクト)への参照先は同じ
5.bの参照先の{prop}の参照先を'Hello'から'Bye'に変更
※この時、変数のaの{prop}の参照先も'Hello'から'Bye'に変更される
↑の流れになっています。
したがって、console.log()で確認した場合、コメントの内容になります。
このように、オブジェクトを保持している変数を他の変数に代入した場合には、プロパティを変更するとそのコピー元のオブジェクトにも影響します。
#参照とconst
前述した参照とコピーの内容を踏まえると参照とconstの挙動も見えてきます
const a = 'Hello';
a = 'Bye'; /*エラー*/
const b = {
prop: 'Hello'
};
b.prop = 'Bye'; /*正常に動作*/
console.log(b);
// {
// prop: 'Bye'
// };
上記のコードでは、
- constでプリミティブ値を代入した場合、代入した'Hello'への参照先がロックされるため、あとから他のプリミティブ値(今回は'Bye')を代入しても参照先が変更されないため、エラーとなります。
- constでオブジェクトを代入した場合、{...}(オブジェクト)への参照先はロックされますが、{...}(オブジェクト)が参照しているオブジェクトの実体(今回は{prop})はロックされていないため、{prop}の参照先である'Hello'という文字列は変更できます
このように、参照とコピーによってconstでの挙動もプリミティブ型とオブジェクトで違いがあります。
#参照と引数
一度「参照とコピー」のおさらいをします
let a = b;
これは変数の参照先の値、もしくはオブジェクトへの参照のコピーを表す。
このことを踏まえて、
function fn(a) {
}
let a = 0;
fn(a);
これは
let a = b;
と同じ。
つまり、fn(a);の引数aにletで定義した変数aのプリミティブ値0を代入し、引数aの参照先を別のメモリ空間にコピーした数値0に変更した
ということです。
#参照と分割代入
分割代入の定義
let {a,b} = object;
オブジェクトから特定のプロパティーを抽出して宣言を行う。
const a = {
prop: 'hello'
}
let {prop} = a;
prop = 'bye';
console.log(a);
// {
// prop: 'hello'
// }
console.log(prop); /*'bye'*/
上記のコードでは、
1.変数aに{...}(オブジェクト)への参照先を保持したあとに、letで変数として{prop}を宣言し、そこにaを代入しています。
2.この時、{prop}という変数にpropの値である'hello'(プリミティブ値)をコピーしたメモリ空間の参照先が保持されます。
3.そして、変数{prop}の値を'bye'に参照先を変更する。
↑の流れになっています。
したがって、console.log()で見た時に、コメントの内容になります。
このように、分割代入ではオブジェクトのプロパティーの値がコピーされて、新しい変数からの参照が貼られるということを理解してください。
#参照の比較と値の比較
等価演算子によるオブジェクトの比較とプリミティブ値の比較について解説します。
const a = {
prop: 0
}
const b = {
prop: 0
}
console.log(a === b); /*false*/
console.log(a == b); /*false*/
console.log(a.prop === b.prop) /*true*/
console.log(a.prop == b.prop) /*true*/
const c = a;
console.log(a === c); /*true*/
console.log(a == c); /*true*/
上記のコードにおいて重要なことは、
等価演算子で比較した場合、
- プリミティブ型では値の比較
- オブジェクトでは参照の比較
↑のように比較されるということです
つまり、
- 変数aと変数bを比較した場合、オブジェクトは参照の比較をするので、aとbは参照先が異なるのでfalse
- a.propとb.propを比較した場合、プリミティブ型は値の比較をするので、参照先が違っても値が同じためtrue
- 変数aと変数cを比較した場合、オブジェクトは参照の比較をするので、aとcは参照先が同じなのでtrue
↑のようになります。
#まとめ
いかがでしたでしょうか。
今回は、前回に続いてプリミティブ型とオブジェクトそれぞれの挙動について解説しました。
プリミティブ型とオブジェクトの参照とコピーはデータの扱いにおいてとても重要なのでしっかりと理解しておきましょう!