イミュータビリティは関数型プログラミングの核となる原則であり、オブジェクト指向プログラミングにも多くのものを提供します。ミュータブルオブジェクトとは、作成後に状態を変更できるオブジェクトです。イミュータブルオブジェクトとは、作成後に状態を変更できないオブジェクトです。
JavaScriptにおけるイミュータブルオブジェクトの例は何ですか?
JavaScriptでは、一部の組み込み型(数値、文字列)はイミュータブルですが、カスタムオブジェクトは一般的にミュータブルです。
JavaScriptの組み込みイミュータブルオブジェクトには、Math
、Date
などがあります。
JavaScriptのプレーンオブジェクトにイミュータビリティを追加/シミュレートする方法をいくつか示します。
オブジェクト定数プロパティ
writable: false
とconfigurable: false
を組み合わせることで、本質的にオブジェクトプロパティとして定数(変更、再定義、削除できない)を作成できます。次のように記述します。
let myObject = {};
Object.defineProperty(myObject, 'number', {
value: 42,
writable: false,
configurable: false,
});
console.log(myObject.number); // 42
myObject.number = 43;
console.log(myObject.number); // 42
拡張の禁止
オブジェクトに新しいプロパティが追加されるのを防ぎたいが、オブジェクトの残りのプロパティはそのままにしておきたい場合は、Object.preventExtensions(...)
を呼び出します。
var myObject = {
a: 2,
};
Object.preventExtensions(myObject);
myObject.b = 3;
myObject.b; // undefined
非厳格モードでは、b
の作成は黙って失敗します。厳格モードでは、TypeError
がスローされます。
Seal
Object.seal()
は「シールされた」オブジェクトを作成します。これは、既存のオブジェクトを取得して、本質的にそれにObject.preventExtensions()
を呼び出すだけでなく、既存のすべてのプロパティをconfigurable: false
とマークします。
したがって、新しいプロパティを追加できないだけでなく、既存のプロパティを再構成したり削除したりすることもできません(ただし、その値は変更できます)。
Freeze
Object.freeze()
は凍結されたオブジェクトを作成します。これは、既存のオブジェクトを取得して、本質的にそれにObject.seal()
を呼び出すだけでなく、すべての「データアクセサー」プロパティをwritable:false
とマークし、その値を変更できないようにします。
このアプローチは、オブジェクト自体で達成できる最高のレベルのイミュータビリティです。これにより、オブジェクトまたはその直接のプロパティへの変更が防止されます(ただし、上記のように、参照されている他のオブジェクトの内容は影響を受けません)。
var immutable = Object.freeze({});
オブジェクトを凍結すると、新しいプロパティをオブジェクトに追加できなくなり、既存のプロパティの削除や変更を防ぎます。Object.freeze()
は、オブジェクトの列挙可能性、設定可能性、書き込み可能性、およびプロトタイプを保持します。渡されたオブジェクトを返し、凍結されたコピーは作成しません。
イミュータビリティの利点と欠点は何ですか?
利点
- 変更検出が容易 - オブジェクトの等価性は、参照等価性を通じて効率的かつ簡単に判断できます。これはReactやReduxでオブジェクトの違いを比較するのに役立ちます。
- イミュータブルなオブジェクトを持つプログラムは、オブジェクトが時間とともにどのように進化するかを心配する必要がないため、考えるのが簡単です。
- イミュータブルなオブジェクトが関数から返されたり渡されたりしても、イミュータブルなオブジェクトが変更される可能性がないため、防御的なコピーは不要です。
- 参照による簡単な共有 - オブジェクトの1つのコピーは別のコピーと同じくらい優れているため、オブジェクトをキャッシュしたり、同じオブジェクトを複数回再利用したりできます。
- スレッドセーフ - イミュータブルなオブジェクトは、マルチスレッド環境でスレッド間で安全に使用できます。他の並行して実行されているスレッドで変更されるリスクがないためです。
- ImmutableJSのようなライブラリを使用すると、オブジェクトは構造共有を使用して変更され、似た構造を持つ複数のオブジェクトのメモリ消費が少なくなります。
欠点
- イミュータブルなデータ構造とその操作を素朴に実装すると、新しいオブジェクトが毎回作成されるため、非常にパフォーマンスが低下する可能性があります。構造共有を活用する効率的なイミュータブルなデータ構造と操作には、ライブラリを使用することをお勧めします。
- 既存のオブジェクトを変更するのではなく、多くの小さなオブジェクトを割り当てる(および解放する)と、パフォーマンスに影響を与える可能性があります。アロケータまたはガベージコレクタの複雑さは、通常ヒープ上のオブジェクトの数に依存します。
- グラフなどの循環データ構造は構築が困難です。初期化後に変更できない2つのオブジェクトがある場合、どのようにしてそれらを互いに指すようにできますか?
自分のコードでイミュータビリティを達成するにはどうすればよいですか?
代替案は、const
宣言と上記の作成方法を組み合わせることです。「ミューテーション」するオブジェクトの場合は、スプレッド演算子、Object.assign
、Array.concat()
などを使用して、元のオブジェクトを変更するのではなく、新しいオブジェクトを作成します。
例:
// 配列の例
const arr = [1, 2, 3];
const newArr = [...arr, 4]; // [1, 2, 3, 4]
// オブジェクトの例
const human = Object.freeze({ race: 'human' });
const john = { ...human, name: 'John' }; // {race: "human", name: "John"}
const alienJohn = { ...john, race: 'alien' }; // {race: "alien", name: "John"}