【入門編】再代入禁止の先にあるもの:constでオブジェクトを定義した際の「不変性」の誤解を解く – JavaScriptコア文法・モダン言語仕様と変数・関数解析バイブル

こんにちは!フロントエンドからNode.jsの深層まで、日夜JavaScriptと向き合っているシニアアーキテクトです。

JavaScriptを学び始めると、変数の宣言には `const` を使うのがモダンな作法だと教わりますよね。「`const` は定数、つまり一度入れたら二度と変えられない箱なんだな」と理解して、こんなコードを書いたことはありませんか?

const user = { name: ‘Alice’ };
user.name = ‘Bob’; // あれっ、エラーにならない…!?

「`const` なのに値が書き換わっちゃった!これってバグなの?」と、思わず冷や汗をかいた経験があるかもしれません。ここをクリアすれば、JavaScriptのメモリ管理や変数の本質が一気に見えてきますよ。今日は、`const` の本当の役割と、よくある誤解について、優しく紐解いていきましょう。

—

1. `const` が守っているのは「中身」ではなく「アドレス」

まず、JavaScriptの変数がメモリ上でどう扱われているのか、イメージ図で考えてみましょう。

JavaScriptでオブジェクトを作るとき、コンピュータのメモリ(V8エンジンのヒープ領域)の中では、以下のようなことが起きています。

1. メモリのどこか(例: `0x1234` という番地)に `{ name: ‘Alice’ }` という実体が作られる。
2. 変数 `user` には、その実体が置いてある「場所(参照)」がメモされる。

`const` という鍵付きの箱が守っているのは、変数 `user` の中身、つまり「`0x1234` という住所のメモ書き」そのものです。

だから、以下のような「住所の書き換え」は `const` が厳しくブロックしてくれます。

const user = { name: ‘Alice’ };

// 別のオブジェクトを代入しようとする(住所の書き換え)
user = { name: ‘Bob’ };
// 💥 TypeError: Assignment to constant variable.

しかし、`user` が指し示している住所(`0x1234`)の家の中に入って、家具の配置を変えることは、`const` の管轄外なんです。

const user = { name: ‘Alice’ };

// 住所(0x1234)の家の中にある ‘name’ プロパティを書き換える
user.name = ‘Bob’;

console.log(user.name); // => ‘Bob’ (エラーにならずに書き換わる!)

これが、`const` でオブジェクトを定義したときに起こる「不変性(イミュータビリティ)の誤解」の正体です。`const` は「再代入禁止」であって、「中身の変更禁止(完全な不変)」ではないという点をしっかりと心に留めておいてくださいね。

—

2. 本当に中身を変えさせたくないときは?:`Object.freeze()` の登場

「いや、私はオブジェクトの中身すら一切書き換えられたくないんだ!」という場面もあるはずです。そんなときに使うのが、JavaScriptの標準機能である `Object.freeze()` です。

`Object.freeze()` を使うと、オブジェクトを文字通り「凍結」し、プロパティの追加・削除・変更をすべて禁止することができます。

実際のコードで動きを見てみましょう。

‘use strict’; // 厳格モード(エラーを分かりやすく検知するため)

const frozenUser = Object.freeze({
name: ‘Alice’,
age: 28
});

// プロパティの値を書き換えようとしてみる
frozenUser.age = 30;

// 厳格モードではここで TypeError が発生する。
// 通常モードでも、書き換えは無視されて元の値のまま残る。
console.log(frozenUser.age); // => 28

ここで重要なポイントは、`const` と `Object.freeze()` はタッグを組んで初めて真価を発揮するということです。

  • `const`: 変数への再代入を防ぐ
  • `Object.freeze()`: オブジェクトの中身の改変を防ぐ

この2つを組み合わせることで、意図しないデータの書き換えを防ぐ堅牢なコードが書けるようになります。

—

3. 注意! `Object.freeze()` は「浅い(Shallow)」凍結

ここで一つ、実務でよくハマる「深みの罠」についてお話ししておきます。

実は `Object.freeze()` は、オブジェクトの直下の階層しか凍結しません。 オブジェクトの中にさらに別のオブジェクト(ネストされた構造)がある場合、その内側のオブジェクトまでは凍結されないのです。

const complexCompany = Object.freeze({
name: ‘Tech Corp’,
boss: {
name: ‘John’ // ここはネスト(入れ子)されたオブジェクト
}
});

// 直下のプロパティは変更できない
// complexCompany.name = ‘Other Corp’; // エラー or 無視される

// しかし、ネストされたオブジェクトの中身は…?
complexCompany.boss.name = ‘Smith’;

console.log(complexCompany.boss.name); // => ‘Smith’ (なんと書き換わってしまう!)

「えっ、じゃあ完全に凍結するにはどうすればいいの?」と思ったそこのあなた、鋭いですね!
完全に中身を不変にしたい(ディープフリーズしたい)場合は、再帰的に `Object.freeze()` を実行するユーティリティ関数を自作するか、Lodashなどの信頼できるライブラリの `cloneDeep` や不変性を保つモダンな状態管理アプローチを利用します。

初学者のうちは、「通常のオブジェクトや配列を `const` で宣言し、できるだけ値を書き換えない(イミュータブルな書き方を意識する)こと」を心がけるだけでも、バグの少ないクリーンなコードにグッと近づけますよ。

—

まとめ

今回のポイントをサクッと振り返ってみましょう。

1. `const` は「再代入(住所の書き換え)」を禁止するものであり、「オブジェクトの中身の変更」まで禁止してはくれない。
2. オブジェクトや配列を `const` で宣言しても、プロパティの追加や変更・要素の書き換えは普通にできてしまう。
3. オブジェクトの改変自体を完全に防ぎたいときは、`Object.freeze()` を併用する。
4. ただし、`Object.freeze()` はネストされた奥深くのオブジェクトまでは凍結しない「浅い凍結(Shallow Freeze)」なので注意が必要。

JavaScriptのメモリの仕組みや変数の参照という概念は、最初は少し難しく感じるかもしれません。ですが、「変数はただの住所のメモ書きなんだな」とイメージできるようになると、コードの挙動が手に取るように分かるようになります。

ここをクリアできれば、あなたのJavaScriptの基礎力は確実に次のステージへステップアップしていますよ。一緒にモダンで美しいコードを書く楽しさを極めていきましょう!

タイトルとURLをコピーしました