OHTA412

プリミティブ型は値をコピーし、オブジェクトは参照をコピーする

JavaScriptで値をコピーするとき、型によって挙動が変わります。予期せぬエラーの原因になるので、挙動を把握しておく必要があります。

プリミティブ型とオブジェクト

JavaScriptのデータ型は、プリミティブ型とオブジェクトに分けられます。プリミティブ型は、以下の7個です。

  • String
  • Number
  • Boolean
  • Undefined
  • Null
  • Symbol
  • Bigint

プリミティブ型以外をオブジェクトといいます。オブジェクトは、変数や関数の集合です。オブジェクトの中で、変数のことをプロパティ、関数のことをメソッドといいます。

let taro = {
    name: '太郎',
    age: 20,
    interests: ['movie', 'soccer'],
    greeting: function(){
        console.log('こんにちは!私は' + this.name + 'です。');
    }
};

taro.greeting(); // 「こんにちは!私は太郎です。」と表示される

オブジェクトを作成して、変数taroに入れました。オブジェクトは、このように複数の値を1つの集合として使うことができます。JavaScriptでは配列もリスト風オブジェクトですし、関数も実行可能なオブジェクトです。

プリミティブ値はイミュータブル

プリミティブ値はイミュータブル(immutable:不変)といい、一度設定すると変更することができません。letを使って変数を宣言した場合は値の再代入ができるではないか?と思われるかもしれませんが、それとは違いメモリー空間での話です。

let a = 'hoge';
a = 'fuga';

変数aにhogeという文字列を代入します。その後、fugaという文字列を再代入した場合です。

まずメモリー空間のどこかにaとhogeが保存されます。そして、aにはhogeへの参照が保持されます。

その後、aにfugaを再代入したら、fugaがhogeとは別の場所へ保存されます。そして、aの参照がhogeからfugaへ変更されます。

参照とは?
参照とは、値が保存されている場所の住所のようなものです。参照が変われば、取得する値も変わってきます。

このようにプリミティブ値は、その都度新しく保存されていき変更することができません。

オブジェクトはミュータブル

プリミティブ値と違い、オブジェクトはミュータブル(mutable:可変)です。

let taro = {
    name: '太郎',
    age: 20,
    …
};
taro.age = 21;

taroにオブジェクトを定義したときの流れです。

まずtaroという変数が定義され、そこにオブジェクトへの参照が保持されます。オブジェクトにはプロパティやメソッドが定義されていて、それぞれが値への参照を持っています。

6行目でtaroオブジェクトのageプロパティを20から21に変更しています。メモリー空間に新しく21が作られ、オブジェクト内のageの参照が変更されます。つまり、オブジェクトが変更されたことになります。

このようにオブジェクトとはミュータブルであり、「値への参照を名前付きで保存する入れ物」と解釈することができます。

プリミティブ型は値をコピーする

プリミティブ型をコピーしたとき、値そのものが複製されます。

let a = 'hoge';
let b = a;
b = 'fuga';

console.log(a); // 「hoge」と表示される
console.log(b); // 「fuga」と表示される

この場合、まずaとhogeが定義され、aにはhogeへの参照が格納されます。

2行目でbを定義します。aの値を複製して、その値への参照をbへ格納します。

3行目でfugaが定義され、bの参照先をhogeからfugaへ変更します。

このように、プリミティブ型は値そのものを複製するので、変更を加えてもお互いに影響を及ぼしません。

オブジェクトは参照をコピーする

プリミティブ型と違い、オブジェクトは参照をコピーします。

let taro = {
    name: '太郎',
    age: 20,
    …
};
let jiro = taro;
jiro.name = '次郎';

console.log(taro.name); // 「次郎」と表示される
console.log(jiro.name); // 「次郎」と表示される

この場合、taroとオブジェクトが定義され、taroにはオブジェクトへの参照が格納されます。

6行目でjiroを定義します。そして、taroが保持している「オブジェクトへの参照」がコピーされます。このとき、オブジェクト自体がコピーされるわけではないことに注意してください。あくまで参照をコピーしただけなので、taroもjiroも同じオブジェクトを参照しています。

そのため、7行目でjiroのnameを変更したとき、taroが参照しているオブジェクトにも変更が加えられます。

その結果、taro.nameとjiro.name両方とも「次郎」となります。

まとめ

プリミティブ型は値そのものをコピーするので、コピー後に値を変更してもお互いに干渉しません。しかし、オブジェクトの場合はオブジェクトへの参照をコピーするので、変更を加えた場合は、そのオブジェクトを参照しているもの全てに影響を及ぼします。

上でも書きましたが、JavaScriptでは配列もオブジェクトなので、配列をコピーするときにも注意が必要です。複製元の値に変更が加えられてもいいのか考慮する必要があります。