【入門編】【初心者向け】constで定義したオブジェクトは本当に「不変」なのか?:参照と値の挙動を理解する – JavaScriptコア文法・モダン言語仕様と変数・関数解析バイブル

こんにちは!JavaScriptの世界へようこそ。
フロントエンドからNode.jsのバックエンドまで、日々コードを書き続けていると、変数やメモリの挙動について面白い(そして時々ハマりやすい)ポイントに出会いますよね。

今回は、JavaScriptを学び始めたばかりの方が必ずといっていいほど直面する、「`const`で定義したオブジェクトは本当に不変(イミュータブル)なのか?」という疑問について、V8エンジンのメモリ構造の裏側まで少し覗き見ながら、優しく、そして本質的に解き明かしていきましょう。

ここをクリアできれば、JavaScriptの変数管理の基本はバッチリマスターできますよ!

—

1. `const` は「再代入禁止」であって「不変」ではない

JavaScriptで定数を宣言するとき、私たちは当たり前のように `const` を使いますよね。

const appName = “SuperApp”;
// appName = “NewApp”; // ⚠️ これはエラーになります!

「`const` を使えば値が変わらない(イミュータブルになる)」と覚えている方は多いのではないでしょうか? 実は、ここに大きな誤解が潜んでいます。

`const` が保証しているのは「その変数箱への『再代入』の禁止」だけであって、「箱の中に入っているデータそのものの変更禁止」ではないのです。特に、オブジェクト(ArrayやPlain Objectなど)を扱うとき、この違いが明確になります。

まずは、実際のコードでその挙動を見てみましょう。

// constでオブジェクトを定義
const user = {
name: “Taro”,
age: 20
};

// 中身のプロパティを書き換えてみる
user.age = 21; // エラーにならない!
user.job = “Engineer”; // 新しく追加してもエラーにならない!

console.log(user);
// 出力結果: { name: ‘Taro’, age: 21, job: ‘Engineer’ }

あれっ? `const` で宣言したはずなのに、データが書き換わってしまいましたね。「えっ、バグじゃないの?」と思われるかもしれませんが、これはJavaScriptの仕様通り、正常な挙動なんです。

一体、メモリの内部では何が起きているのでしょうか?

—

2. メモリの「値」と「参照」の正体をつかむ

この挙動を完全に理解するために、JavaScriptのエンジン(V8など)がメモリ上でデータをどう扱っているか、イメージしやすいように図解してみましょう。

JavaScriptの世界には、大きく分けて「プリミティブ型(数値や文字列など)」と「オブジェクト型(オブジェクトや配列など)」の2種類のデータが存在します。

プリミティブ型の場合(箱に中身が直接入る)

const score = 100;

この場合、メモリ上の「`score` という名前の箱」の中に、数値の `100` が直接入っています。だから `const` で固定すると、中の `100` を `200` に書き換えることは絶対にできません。

オブジェクト型の場合(箱には「住所」が入る)

一方、オブジェクトの扱いは全く異なります。

const user = { name: “Taro” };

このコードが実行されたとき、メモリの裏側では次のようなことが起きています。

1. メモリのどこか(ヒープ領域と呼ばれる広大なエリア)に、実体である `{ name: “Taro” }` というオブジェクトが作られる。
2. 変数 `user` の箱の中には、そのオブジェクトが置かれている「メモリ上の住所(参照:Reference)」だけがポツンと入れられる。

つまり、`const user` が守っているのは、「`user` という箱の中身(=住所)を別の住所に書き換えてはならない」というルールだけなんです。

住所(`user`)が指し示す先にある「お家(オブジェクトの中身)」の家具を模様替えしたり(プロパティの書き換え)、庭を広くしたりすること(プロパティの追加)は、住所そのものを変えていないため、`const` のルールには違反しないというわけです。

[ 変数 “user” の箱 (constで固定) ]
│
├─► 中に入っているのは「住所」 (例: 0x7fff…)
│
▼
[ メモリ上のヒープ領域 ]
└─► 実際の家: { name: “Taro”, age: 21 } ← ここの中身は自由に変えられる!

だから、次のような「別のオブジェクトを丸ごと代入し直す行為」は、住所そのものを書き換えることになるため、エラーになります。

const user = { name: “Taro” };

// 別の家(オブジェクト)の住所を代入しようとする
user = { name: “Jiro” }; // ❌ TypeError: Assignment to constant variable.

—

3. 本当にオブジェクトを「不変(イミュータブル)」にするには?

「じゃあ、オブジェクトの中身すら一切書き換えられたくない場合はどうすればいいの?」という疑問が湧きますよね。

モダンなJavaScriptでは、オブジェクトの改変を完全にロックするための機能が用意されています。それが `Object.freeze()` です。

const frozenUser = Object.freeze({
name: “Taro”,
age: 20
});

// 中身を書き換えようとしてみる
frozenUser.age = 25;

console.log(frozenUser.age);
// 出力結果: 20 (書き換わらずに無視される! strictモードではエラーになる)

`Object.freeze()` を使うと、そのオブジェクトのプロパティの変更、追加、削除がすべて不可能になります。まさに「不変(イミュータブル)」なオブジェクトの誕生です。

ただし、注意点として `Object.freeze()` は「浅い凍結(Shallow Freeze)」です。オブジェクトの中にさらに別のオブジェクト(ネストされた構造)がある場合、内側のオブジェクトまでは凍結されません。完全なイミュータブルを担保したい場合は、再帰的にfreezeする処理を書くか、Immutable.jsなどのライブラリ、あるいはモダンな状態管理の設計を取り入れることになります。

—

まとめ

今回の重要なポイントをギュッと凝縮して振り返ってみましょう。

1. `const` は「再代入禁止」を意味する
変数そのものに別の値を上書きすることを防ぎますが、オブジェクトの中身の変更までは止めてくれません。
2. オブジェクト変数は「値」ではなく「住所(参照)」を持っている
`const user` が固定しているのはオブジェクトの「住所」であり、住所の先にある家の中身は自由にいじることができます。
3. 中身まで完全に固定したいときは `Object.freeze()` を使う
ただし、ネストしたオブジェクトには注意が必要です。

JavaScriptがメモリ上でデータをどう扱い、変数がどのような役割を持っているのかという「裏側の仕組み」を知ると、コードを書くときの視界が一気にクリアになりますよね。

この基本をしっかり押さえておけば、予期せぬバグに悩まされることもグッと減るはずです。明日からのコーディングに、ぜひこの知見を活かしてくださいね!

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