はじめに (対象読者・この記事でわかること)

この記事は、JavaScriptの基本的な文法を理解しているプログラミング初学者から中級者を対象にしています。特に、constキーワードを使った配列の宣言と、Object.freezeメソッドを使った配列の不変性の確保方法について学びたい方に向けています。

この記事を読むことで、constを使った配列の宣言方法、constとObject.freezeの違い、Object.freezeを使って配列の変更を防ぐ具体的な方法、そして実際の開発で不変なデータ構造を扱うメリットについて理解できるようになります。また、不変性の確保がなぜ重要なのかという背景知識も得られるでしょう。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。 - JavaScriptの基本的な文法(変数宣言、関数など) - 配列の基本的な操作方法(push、pop、mapなど) - オブジェクトの基本的な概念

constを使った配列の宣言とその限界

JavaScriptでは、変数を宣言する際にvar、let、constの3つのキーワードを使うことができます。その中でもconstは「再代入ができない」変数を宣言するために使われます。多くの開発者はconstを使うことで、意図しない変数の変更を防ぎたいと考えています。

配列をconstで宣言する場合、以下のように記述します。

Javascript
const fruits = ['apple', 'banana', 'orange'];

この宣言により、fruitsという変数自体には再代入ができなくなります。つまり、以下のようなコードはエラーになります。

Javascript
fruits = ['grape', 'melon']; // TypeError: Assignment to constant variable.

しかし、constで宣言された配列の中身は変更可能です。例えば、以下のような操作は問題なく実行できます。

Javascript
fruits.push('grape'); // 配列に要素を追加 console.log(fruits); // ['apple', 'banana', 'orange', 'grape'] fruits[0] = 'pineapple'; // 配列の要素を変更 console.log(fruits); // ['pineapple', 'banana', 'orange', 'grape']

このように、constは変数自体の再代入を防ぐだけであり、配列の内容(要素の追加、削除、変更)は防げません。配列の内容も変更不可にしたい場合には、Object.freezeを使う必要があります。

Object.freezeを使った配列の不変性の確保

Object.freezeは、JavaScriptの組み込み関数で、オブジェクト(配列も含む)を「凍結」します。凍結されたオブジェクトは、プロパティの追加、削除、変更ができなくなります。これにより、配列の内容も不変に保つことができます。

Object.freezeを使って配列を不変にする方法は以下の通りです。

Javascript
const fruits = Object.freeze(['apple', 'banana', 'orange']);

この配列に対して、要素の追加、削除、変更を試みるとエラーが発生します。

Javascript
fruits.push('grape'); // TypeError: Cannot add property 3, object is not extensible fruits[0] = 'pineapple'; // Strict modeではエラーになるが、非Strict modeでは無視される delete fruits[0]; // 削除もできない

ただし、注意点として、Object.freezeは「浅い不変性」しか提供しません。つまり、配列の要素がオブジェクトの場合、そのオブジェクト自体は変更可能です。

Javascript
const users = Object.freeze([ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' } ]); users[0].name = 'Alice Updated'; // これは可能 console.log(users); // [{ id: 1, name: 'Alice Updated' }, { id: 2, name: 'Bob' }]

配列の要素も含めて完全に不変にしたい場合は、再帰的にObject.freezeを適用する必要があります。

Javascript
function deepFreeze(obj) { if (obj && typeof obj === 'object') { Object.keys(obj).forEach(key => { deepFreeze(obj[key]); }); Object.freeze(obj); } return obj; } const users = deepFreeze([ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' } ]); users[0].name = 'Alice Updated'; // これもエラーになる

Object.freezeの具体的な使用例

Object.freezeは、状態管理ライブラリ(Reduxなど)や、Reactのコンポーネントの状態管理でよく使われます。例えば、Reactのstateとして配列を使う場合、Object.freezeを使って意図しない変更を防ぐことができます。

Javascript
import React, { useState } from 'react'; function MyComponent() { const [items, setItems] = useState(Object.freeze([ { id: 1, text: 'Item 1' }, { id: 2, text: 'Item 2' } ])); const addItem = () => { // 不変性を保ちつつ新しい配列を作成 const newItems = [...items, { id: items.length + 1, text: `Item ${items.length + 1}` }]; setItems(Object.freeze(newItems)); }; return ( <div> <ul> {items.map(item => ( <li key={item.id}>{item.text}</li> ))} </ul> <button onClick={addItem}>Add Item</button> </div> ); }

この例では、Object.freezeを使ってstateを保護し、意図しない変更を防いでいます。また、新しい配列を作成してからsetItemsを呼び出すことで、Reactの状態更新のルール(不変性の保持)を守っています。

constとObject.freezeの違いまとめ

constとObject.freezeの主な違いを以下にまとめます。

  1. constは変数の再代入を防ぐ - 変数自体への再代入はできない - オブジェクトや配列の内容は変更可能

  2. Object.freezeはオブジェクトの変更を防ぐ - プロパティの追加、削除、変更を防ぐ - 配列の内容も変更不可にする - 浅い不変性のみ(再帰的に適用しないとネストされたオブジェクトは変更可能)

ハマった点やエラー解決

Object.freezeを使う際によくある問題とその解決策を紹介します。

問題1: 非Strictモードでの無視

非Strictモードでは、Object.freezeされたオブジェクトのプロパティ変更はエラーにならずに無視されます。これにより、バグの原因になることがあります。

Javascript
'use strict'; // Strictモード有効 const arr = Object.freeze([1, 2, 3]); arr[0] = 100; // StrictモードではTypeErrorが発生

解決策: - コード全体をStrictモードで実行する - オブジェクトの変更を試みるコードがあるかどうかをテストで確認する

問題2: ネストされたオブジェクトの変更

Object.freezeは浅い不変性しか提供しないため、ネストされたオブジェクトは変更可能です。

Javascript
const user = Object.freeze({ name: 'Alice', address: { city: 'Tokyo' } }); user.address.city = 'Osaka'; // これは可能

解決策: - 再帰的にObject.freezeを適用する(deepFreeze関数など) - 不変性を必要とするデータ構造を設計する際は、ネストを避けるか、Immutable.jsなどのライブラリを使う

問題3: パフォーマンスへの影響

大規模なオブジェクトをObject.freezeすると、パフォーマンスに影響が出ることがあります。

解決策: - 小さな単位で不変性を確保する - 必要な部分だけをObject.freezeする - Immutable.jsなどのライブラリを使い、最適化された不変データ構造を利用する

まとめ

本記事では、JavaScriptでconstを使った配列の宣言とObject.freezeによる不変性の確保方法について解説しました。

  • constは変数の再代入を防ぐが、配列の内容は変更可能
  • Object.freezeは配列の内容も変更不可にする
  • Object.freezeは浅い不変性のみなので、ネストされたオブジェクトは変更可能
  • 完全な不変性が必要な場合は、再帰的にObject.freezeを適用する

この記事を通して、JavaScriptで不変なデータ構造を扱う方法とその重要性について理解できたことと思います。不変性は、意図しないデータの変更を防ぎ、コードの予測可能性を高め、バグを減らすための重要な概念です。

今後は、ReduxやReactでの不変性の活用方法や、Immutable.jsなどのライブラリを使った高度な不変データ構造の実装についても記事にする予定です。

参考資料