こんにちは!日々の開発、本当にお疲れ様です。
JavaScriptを触っていると、最初のうちは「動くけれど、なんだかモヤモヤする挙動」に出会うことがよくありますよね。特に、他の言語からやってきた開発者を盛大に混乱させるのが、今回のテーマである `const` と「不変性(イミュータビリティ)」の誤解 です。
「あれ? `const` で宣言したはずのオブジェクトのプロパティが、普通に書き換えられちゃうんだけど……これってバグ?」
そんな疑問を持ったことはありませんか?
安心してください。それは仕様ですし、V8エンジン(JavaScriptの実行エンジン)のメモリの仕組みを知れば、「なるほど、だからこうなっているのか!」と納得できるはずです。ここをクリアすれば、JavaScriptのメモリ管理とオブジェクトの挙動がぐっと見えてきますよ。
それでは、V8の頭の中を覗き込むような気持ちで、一緒に本質を紐解いていきましょう!
—
1. `const` が固定しているのは「中身」ではなく「住所」
まず、言葉の定義からしっかり整理しておきましょう。
多くのプログラミング初心者は、`const`(コンスタント)という単語から「Constant(定数)」を連想し、「`const` で宣言したものは、値を変えられない(不変になる)」と考えがちです。
ですが、JavaScriptの `const` が固定しているのは、変数の中に入っている「値そのもの」ではなく、メモリ上の「アドレス(参照先)」です。
イメージしやすいように、現実世界で例えてみましょう。
あなたは「Aさん専用のロッカー(変数 `const box`)」を作りました。
- `const` というルールは、「このロッカーの位置(住所)を他のロッカーに変えてはならない」という縛りです。ロッカーの引越しは禁止されています。
- しかし、ロッカーの「中身」に入れられた荷物(オブジェクトのプロパティ)を入れ替えることまでは禁止していません。
コードで見てみましょう。
// 「box」という定数ロッカーを用意しました(住所の固定)
const box = {
item: “りんご”
};
// これはOK:ロッカーの中身(プロパティ)を書き換えているだけ
box.item = “みかん”;
console.log(box.item); // “みかん”が出力されます
// これはNG:ロッカー自体の場所(参照先)を丸ごと変えようとしているのでエラーになります
// box = { item: “バナナ” }; // TypeError: Assignment to constant variable.
`const` が守っているのは、「この変数は絶対に別のオブジェクトを指し示させない」という参照の断線防止です。オブジェクトの中身がどう変化しようと、変数が覚えている「メモリの住所」が変わっていなければ、JavaScriptのエンジンは文句を言いません。
—
2. オブジェクトの変更がV8エンジン(メモリ)の裏側でやっていること
さて、ここからが少し踏み込んだお話です。
「中身が書き換えられる」ということは、V8エンジンのメモリ空間では何が起きているのでしょうか?
JavaScriptのエンジン(V8など)は、メモリ効率を極限まで高めるために、オブジェクトの構造を裏でめちゃくちゃ最適化しています。その代表格が 「隠しクラス(Hidden Classes / Shapes)」 と呼ばれる仕組みです。
オブジェクトに新しいプロパティを追加したり変更したりすると、V8の内部で何が起きるのか、ステップを見てみましょう。
// 1. 空のオブジェクトを作ります(V8はこの時点で基本の「隠しクラス1」を割り当てます)
const user = {};
// 2. プロパティ「name」を追加します
// ここでV8は、オブジェクトの構造が変化したと検知し、
// 高速化のための「隠しクラス2」へ内部の設計図を切り替えます!
user.name = “Alice”;
// 3. プロパティ「age」を追加します
// さらに「隠しクラス3」へと進化します
user.age = 28;
console.log(user); // { name: ‘Alice’, age: 28 }
ここで重要なのは、`const` で守られているのはあくまで `user` という変数が持つ「オブジェクトへのポインタ(参照)」だけであり、オブジェクトが持つプロパティの増減や変更は、V8のメモリマップ上ではダイナミックに構造が変わるイベントであるということです。
プロパティの変更がもたらす「隠しクラス」への影響
もし、あなたが `const` で宣言したオブジェクトのプロパティを後から追加・変更しすぎると、V8エンジンは「おっと、このオブジェクトの構造は頻繁に変わりすぎるぞ。最適化(インラインキャッシュなど)の効率が落ちてしまうな……」と判断し、パフォーマンスに影響が出る場合があります。
これが、「`const` を使ってもオブジェクトの不変性(Immutability)は担保されないため、予期せぬ場所でオブジェクトが変異(ミューテート)すると、パフォーマンスやバグの温床になる」と言われる所以です。
—
3. 本当の「不変性」が欲しいときはどうする?
「じゃあ、オブジェクトの中身も絶対に書き換えられたくない、完全な定数を作りたいときはどうすればいいの?」と思いますよね。
そんなときは、JavaScriptに標準で備わっている `Object.freeze()` を使います。
// オブジェクトを凍結する
const frozenConfig = Object.freeze({
apiEndpoint: “https://api.example.com”,
timeout: 5000
});
// プロパティを変更しようとしても、厳格モード(strict mode)ではエラーになり、
// 通常モードでも変更は無視されます!
frozenConfig.timeout = 10000;
console.log(frozenConfig.timeout); // 5000のまま(変更されていない!)
ただし、ここで一つ注意点があります。`Object.freeze()` は「浅い凍結(Shallow Freeze)」です。オブジェクトの中のさらにネストしたオブジェクト(入れ子の構造)までは凍結してくれません。完全な不変性を手に入れたい場合は、再帰的にフリーズするユーティリティ関数を書くか、Immutable.jsなどの専用ライブラリ、あるいはモダンなイミュータブルパターンを活用する必要があります。
—
まとめ:ここをクリアすればJavaScriptの基本はバッチリ!
いかがでしたでしょうか?今回のポイントをギュッと凝縮して振り返ります。
1. `const` は「値の不変性」ではなく「参照(住所)の固定」を意味する。
2. オブジェクトや配列の `const` は、中身(プロパティや要素)の書き換えが普通にできてしまう。
3. 中身の変更は、V8エンジンの「隠しクラス」やメモリ構造を変化させ、動的なミューテーションを引き起こす。
4. 本当に中身を変えたくない場合は `Object.freeze()` などを組み合わせる必要がある。
「`const` を使っているから私の書いたオブジェクトは安全!」という誤解が解け、メモリやエンジンの挙動まで視野に入れたコードが書けるようになると、あなたのエンジニアとしての視座は一段と高くなります。
ここをしっかりと理解できたあなたなら、もう変数とスコープ、そしてメモリ管理の基礎はバッチリマスターできていますよ!自信を持って、次のステップへ進んでいきましょう。