JavaScriptにおけるミュータブルとイミュータブル
この記事ではJavaScriptにおけるミュータブルとイミュータブルについて説明します。
YouTube Video
JavaScriptにおけるミュータブルとイミュータブル
ミュータブルとは?
ミュータブル(mutable) とは、値を変更できる ことを意味します。オブジェクトや配列などの 参照型(reference type)は、ミュータブルなデータ構造の代表例です。
オブジェクトのミュータブルな例
1let person = { name: "Alice", age: 25 };
2person.age = 26;
3console.log(person); // { name: "Alice", age: 26 }
このコードでは、person
オブジェクトの age
プロパティを 25
から 26
に変更しています。オブジェクトは 参照渡し されるため、変数 person
に格納された メモリアドレス の指す内容が変更されます。
配列のミュータブルな例
1let numbers = [1, 2, 3];
2numbers.push(4);
3console.log(numbers); // [1, 2, 3, 4]
このコードでは push
メソッドを使って、元の配列に新しい要素 4
を追加しています。これは 元の配列を変更 しており、ミュータブルな操作です。
関数の例
1// Function to append a value to an array
2function append(arr, value) {
3 arr.push(value); // Modify the original array
4 console.log(arr);
5}
6
7let numbers = [1, 2, 3];
8append(numbers, 4);
9
10console.log(numbers); // [1, 2, 3, 4] (original array is modified)
このようにミュータブルな操作を関数内で行った場合にも、元の配列が変更されます。
イミュータブルとは?
イミュータブル(immutable) とは、値を変更できない ことを意味します。プリミティブ型(primitive type) は基本的にイミュータブルです。
プリミティブ型のイミュータブルな例
1let str = "hello";
2str[0] = "H";
3console.log(str); // "hello"
文字列 str
の一文字目を H
に変更しようとしていますが、文字列は イミュータブル であるため変更されません。
関数の例
1// Function to increment a number
2function increment(num) {
3 num++; // This modifies only the local copy of num
4 console.log(num);
5}
6
7let number = 10;
8increment(number);
9
10console.log(number); // 10 (original number remains unchanged)
このように数値はイミュータブルであるため、関数内での操作は元の変数に影響しません。
配列のイミュータブルな操作
配列はミュータブルですが、元の配列を変更せずに新しい配列を作成する ことでイミュータブルな操作を実現できます。
1// Create an array of numbers
2let numbers = [1, 2, 3];
3
4// Create a new array by spreading the original and adding a new element
5let newNumbers = [...numbers, 4];
6
7console.log(numbers); // [1, 2, 3] (original array is unchanged)
8console.log(newNumbers); // [1, 2, 3, 4] (new array with an added element)
ここではスプレッド構文(...
)を用いて、新しい配列 newNumbers
を作成しています。元の numbers
は変更されていないため、イミュータブルな操作です。
イミュータブルデータ構造を活用するメリット
予測可能性の向上
イミュータブルなデータは変更されないため、予期しない変更が発生しづらく、バグの発生を防げます。
不変性を前提としたライブラリとの相性
React
や Redux
などのライブラリでは、イミュータブルなデータを前提とした設計が多く、適切に利用することで状態管理が容易になります。
Object.freeze によるオブジェクトのイミュータブル化
Object.freeze
を使用すると、オブジェクトの変更を防ぐことができます。
1// Create a frozen object (properties cannot be modified)
2const person = Object.freeze({ name: "Alice", age: 25 });
3
4// Attempt to modify a property (ignored in non-strict mode, error in strict mode)
5person.age = 26;
6
7console.log(person); // { name: "Alice", age: 25 }
ただし、Object.freeze
は 浅い凍結(shallow freeze) であり、ネストされたオブジェクトのプロパティは変更可能なままです。
1// Create a frozen object with a nested object
2const user = Object.freeze({ profile: { name: "Bob" } });
3
4// Attempt to modify a nested property (this works because Object.freeze() is shallow)
5user.profile.name = "Charlie";
6
7console.log(user.profile.name); // "Charlie" (nested object is still mutable)
完全にイミュータブルなオブジェクトを作るには、深い凍結(deep freeze)が必要です。
1// Function to deeply freeze an object, making all nested objects immutable
2function deepFreeze(obj) {
3 Object.keys(obj).forEach(key => {
4 if (typeof obj[key] === "object" && obj[key] !== null) {
5 deepFreeze(obj[key]); // Recursively freeze nested objects
6 }
7 });
8 return Object.freeze(obj); // Freeze the top-level object
9}
10
11// Create a deeply frozen object
12const user = deepFreeze({ profile: { name: "Bob" } });
13
14// Attempt to modify a nested property (ignored)
15user.profile.name = "Charlie";
16
17console.log(user.profile.name); // "Bob" (unchanged due to deep freeze)
まとめ
- ミュータブル(mutable) なデータは変更可能で、オブジェクトや配列が該当します。
- イミュータブル(immutable) なデータは変更不可能で、プリミティブ型(文字列、数値など)が該当します。
- スプレッド構文や
map
などを活用すると、イミュータブルなデータ操作が可能 になります。 Object.freeze
やdeepFreeze
を使うと、オブジェクトの変更を防げます。- イミュータブルなデータを使うことで、予測しやすくバグの少ないコードを記述できます。
イミュータブルな設計は、コードの安全性と可読性を向上させるため、積極的に活用しましょう!
YouTubeチャンネルでは、Visual Studio Codeを用いて上記の記事を見ながら確認できます。 ぜひYouTubeチャンネルもご覧ください。