プログラミングにおいて、オブジェクトの変異は、オブジェクトの状態やデータが作成後に変異することを意味します。言い換えると、JavaScriptにおいてオブジェクトの属性を変更する操作はオブジェクトの変異として知られています。オブジェクトの変異はオブジェクトの値を直接変更するため、特に複数の操作がオブジェクトから同時に読み取りや書き込みを試みるアプリケーションでは、課題となります。
この記事では、必要に応じて関連するコード例を交えながら、JavaScriptにおけるオブジェクトの変異について議論します。
JavaScriptにおけるデータ型
データ型は、変数やオブジェクトが保持できるデータの型を示します。JavaScriptは、プリミティブ型とユーザー定義型または参照型の2つの異なるカテゴリのデータ型をサポートしています。
プリミティブデータ型
JavaScriptでは、すべてのプリミティブデータ型は元々不変であり、つまり作成後に変更することはできません。数値、ブーリアン、文字列、BigInt、未定義、null、シンボル、オブジェクトがプリミティブ型の例です。
ユーザー定義型または参照型データ型
ユーザー定義データ型または参照データ型は、プリミティブ型やプリミティブ型とユーザー定義型の組み合わせを使用して作成されたオブジェクトです。ユーザー定義型または参照型の典型的な例は、オブジェクトや配列です。
JavaScriptにおける変数の割り当てと再割り当ての方法
プリミティブ型の変数をプリミティブ型の変数に割り当てると、2つの変数は似たような値を保持しますが、異なる格納場所に保存されます。例えば、2つの変数varA
とvarB
を持っているとして、以下のように1つの変数をもう1つに割り当てるとします:
var varA = 100;
var varB = varA;
console.log(varB);
上記のコードを実行すると、コンソールに数値100が表示されます。そして、次のように2つの変数のうちの1つの値を変更すると:
var varA = 100;
var varB = varA;
varB = 500;
console.log(varA);
変数varB
の値が500に変更されたことに注目してください。変数varA
の値を表示すると、依然として100が表示されます。これは、これらの変数varA
とvarB
が2つの異なるメモリ場所に格納されているためです。したがって、どちらかを変更しても、新しい値や変更された値は他の変数に反映されません。
JavaScriptにおけるオブジェクトのミューテーションとは何ですか?
JavaScriptでは、オブジェクトのデータ型はプリミティブ型または非プリミティブ型のいずれかに属することができます。プリミティブ型は不変です、つまり作成した後に変更することはできませんが、非プリミティブ型、つまりオブジェクトと配列は変更できます。オブジェクトは常にその値を変更することを許容します。したがって、新しいインスタンスを作成せずに、可変型のフィールドの状態を変更することができます。
オブジェクトの変異は、次のような問題を引き起こす可能性があります:
- 変異したオブジェクトは、しばしば並行性やスレッドセーフティの問題から競合状態につながることがあります
- ミューテーションは、予測可能性やスレッドセーフの問題により、ソースコードに複雑さをもたらす可能性があります
- ミューテーションは、アプリケーションのソースコードで特定が難しいバグを引き起こすことがよくあります
- ミューテーションは、テストやデバッグを困難にし、ミューテーションを利用するコードを追跡することが課題になります
オブジェクトミューテーションを示すコード例
オブジェクトのミューテーションは、以下のいずれかのシナリオで発生する可能性があります:
- プロパティの追加、編集、または削除
- ミューテーションを示すメソッドの使用
オブジェクトのプロパティを直接または間接的に変更する場合、実質的にオブジェクトをミューテートしています。以下のコードスニペットは、プロパティを変更することでオブジェクトをミューテートする方法を示しています。
const author = { id: 1, name: "Joydip Kanjilal"};
author.id = 2; author.city = "Hyderabad, INDIA";
console.log(author);
前述のコードでは、id
とname
という2つのプロパティを含むauthorという名前のオブジェクトを作成します。id
プロパティは著者レコードのid
を格納するために使用され、name
プロパティは著者の名前を格納します。id
プロパティに関連する値を変更することによって、authorオブジェクトをミューテートする方法に注意してください。次に、authorオブジェクトにcityという新しいプロパティを追加し、そのプロパティに値を割り当てます。
前述のコードを実行すると、authorオブジェクトのプロパティとその値が以下のように表示されます:
{ name: 'Joydip Kanjilal', city: 'Hyderabad, INDIA' }
JavaScriptでは、オブジェクトを関数に渡したり変数に割り当てたりすると、実際のオブジェクトへの参照が渡されるため、そのオブジェクトのコピーではないことを意味します。これは、オブジェクトを渡すことや変数に割り当てることによって作成された新しいオブジェクトに加えた変更が、実際のオブジェクトのすべての参照に適用されるということを意味します。
次のコードは、JavaScriptでオブジェクトを作成してから変数に割り当てる方法を示しています。
const objA = { id: 1, name: 'Joydip Kanjilal',
city: 'Hyderabad, INDIA', pincode: 500089 }
const objB = objA;
objB.pincode = 500034;
console.log(objA);
前のコードでは、オブジェクトobjA
がobjB
に割り当てられ、objA
のpincodeプロパティの値が変更され、つまり、オブジェクトobjA
が変異しました。プログラムを実行すると、次のデータが表示されます。
{ id: 1, name: 'Joydip Kanjilal', city: 'Hyderabad, INDIA', pincode: 500034 }
pincodeプロパティの値が変更されたことに注意してください。
JavaScriptでのオブジェクト変異の防止
JavaScriptでは、次のような方法で変異を防ぐことができます:
Object.assign()
メソッドやスプレッド演算子(…)を利用してオブジェクトのクローニングを行うObject.seal()
メソッドを使用してオブジェクトのプロパティの追加や削除を防ぐObject.freeze()
メソッドを使用してオブジェクトのプロパティの追加、編集、削除を防ぐ
クローニングを使用する
JavaScriptでスプレッド演算子を使用してオブジェクトをクローンする方法を示す次のコードを参照してください。
let originalObj = { x: 10, y: 100 }; let clonedObj = { originalObj };
ここで、クローンされたオブジェクトの名前はclonedObj
であり、オリジナルのオブジェクトの名前はoriginalObj
と同じです。したがって、これら2つのオブジェクトの2つのプロパティの値を表示すると、結果は同じになります。
次に、以下のコード片に示されているように、クローンされたオブジェクトclonedObj
のプロパティの1つの値を任意の値に変更してください。
clonedObj.x = 50;
そして、以下のコード片を書いて、2つのオブジェクトoriginalObj
とclonedObj
に関連するプロパティx
の値を表示してください。
console.log(originalObj.x);
console.log(clonedObj.x);
プログラムを実行すると、元のオブジェクトのプロパティx
の値が変更されていないことがわかります。以下のようにコンソールに値が表示されます。
10
50
Object.freeze()メソッドの使用
Object.freeze()
メソッドを使用すると、オブジェクトを変更不能にして、そのいずれかのプロパティに対する変更を防ぎます。
const author = { id: 1, name: "Joydip Kanjilal",
city: "Hyderabad", state: "Telengana",
country: "India", pincode: 500089};
Object.freeze(author);
author.city = "Bangalore";
author.state = "Karnataka";
author.pincode = 560010;
console.log(author);
前述のコード片を実行すると、結果は次のようになります。
{
id: 1,
name: 'Joydip Kanjilal',
city: 'Hyderabad',
state: 'Telangana',
country: 'India',
pincode: 500089
}
出力からわかるように、city、state、およびpincodeのプロパティに値を割り当てても効果はありません。そのため、オブジェクトのいずれのプロパティに含まれるデータも変更されていません。
Object.seal()メソッドの使用
Object.seal()
メソッドを使用して、JavaScriptでオブジェクトの変更を防ぐこともできます。このメソッドを使用すると、既存のプロパティの値を変更できますが、オブジェクトのプロパティを変更または削除することはできません。次のコード例がこれを示しています。
const author = { id: 1, name: "Joydip Kanjilal",
city: "Hyderabad", state: "Telangana",
country: "India", pincode: 500089};
Object.seal(author);
author.city = "Bangalore";
author.state = "Karnataka";
author.pincode = 560005;
author.booksauthored = 3;
console.log(author);
前のコードスニペットでは、オブジェクトのプロパティの変更は許可されますが、オブジェクトのプロパティの追加や削除は許可されません。プログラムを実行すると、変更されたプロパティの値が結果に反映されますが、プロパティを追加または削除するステートメントは無視されます。コンソールでの出力は以下のようになります:
{
id: 1,
name: 'Joydip Kanjilal',
city: 'Bangalore',
state: 'Karnataka',
country: 'India',
pincode: 560005
}
Object.defineProperty()メソッドの使用
const author = { id: 1, name: "Joydip Kanjilal"};
Object.defineProperty(author, "booksauthored",
{
value: 3,
writable: false,
});
author.booksauthored = 5;
console.log(author.booksauthored);
JavaScriptのObject.defineProperty()
メソッドを活用して、オブジェクトの個々のプロパティの可変性を制御することができます。次のコードスニペットは、このメソッドを使用して、可変性が制限されたプロパティ内の値の変更を許可しない方法を示しています。
主なポイント
- JavaScriptは、プリミティブ(変更可能)とオブジェクト(不変)の2つの異なるカテゴリにオブジェクトタイプを分類します。
- オブジェクトの変異とは、作成された後にオブジェクトを変更または変更する操作を指します。
- 数値などのプリミティブ値は変更できませんが、作成された後は常にオブジェクトを変更できます。
- JavaScriptの文字列は不変なので、作成されると変更することはできません。
- 変異そのものが悪いわけではありませんが、アプリケーションのバグを減らすために注意して管理する必要があります。
- 推奨される慣行に従い、不変のデータ構造を活用することで、JavaScriptにおける変異を減らすか排除することができます。
Source:
https://dzone.com/articles/an-introduction-to-object-mutation-in-javascript