【実務・中級編】constの不変性とメモリレイアウト:ヒープ上のオブジェクト参照とスタック上のポインタの真実 – JavaScriptコア文法・モダン言語仕様と変数・関数解析バイブル

コードレビューをしていたら、次のようなコードに出くわしたことはないだろうか。

const user = { name: ‘Alice’, role: ‘admin’ };
user.role = ‘guest’; // 平然と書き換えられている

「`const`を使っているのに、中身が書き換わっている!これはバグではないのか?」
ジュニアエンジニアからこのような質問を受けるたび、私はV8エンジンのメモリレイアウトと、JavaScriptにおける「不変性(Immutability)」の本質について話を始める。

結論から言おう。`const`は「値の不変性(Immutability)」を保証するものではない。それは単なる「再代入の禁止(Assignment Immutability)」なのだ。

この違いを曖昧にしたまま大規模なフロントエンドアプリケーションや非同期API連携のコードを書くと、状態管理の予測可能性が崩壊し、デバッグ地獄へと直結する。今回は、V8エンジンのスタックとヒープの挙動にまで踏み込み、実務で絶対にバグを踏まないためのメモリ最適化と設計パターンを伝授する。

—

1. V8エンジンのメモリレイアウト:スタックとヒープの真実

JavaScriptの変数がメモリ上でどのように扱われているか、その物理的な配置を正確にイメージできているだろうか。

JavaScriptのランタイム(V8)において、メモリ空間は大きく分けてスタック(Stack)とヒープ(Heap)の2つに大別される。

  • スタック領域: プリミティブ型(`Number`, `String`, `Boolean`, `Symbol`, `BigInt`, `undefined`, `null`)の実体や、オブジェクト・配列への「ポインタ(参照アドレス)」が格納される。サイズが固定されており、LIFO(後入れ先出し)で超高速にアクセスされる。
  • ヒープ領域: 参照型(`Object`, `Array`, `Function`)の「実体」が動的に配置される。サイズが可変であり、ガベージコレクタ(GC)による管理下にある。

`const` が守っているのは「スタック上のポインタ」だけ

`const config = { timeout: 5000 };` と記述したとき、V8の内部で何が起きているか。

1. ヒープ領域に `{ timeout: 5000 }` というオブジェクトの実体が生成される。
2. スタック領域に `config` という変数が確保され、その中にはヒープ上のオブジェクトを指すメモリアドレス(ポインタ)が書き込まれる。
3. `const` は、この「スタック領域にある `config` の中身(=ポインタの値)を書き換えること」をコンパイル時(厳密には構文解析時)に静的に禁止する。

したがって、以下のような再代入はV8によって厳しく阻止される。

const config = { timeout: 5000 };
config = { timeout: 10000 }; // TypeError: Assignment to constant variable.

しかし、オブジェクトのプロパティを書き換えるコードはどうだろうか。

config.timeout = 10000; // 成功する

スタック上に格納されている「ポインタの値」自体は一切変わっていない。ただポインタが指し示すヒープ領域のオブジェクトのプロパティ値が書き換わっただけなので、`const` のルールには違反しないのだ。これが、「`const` なのに中身が書き換わる」メカニズムの正体である。

—

2. パフォーマンスとメモリ効率:なぜ不注意なミューテーションは悪なのか

「動くからいいじゃないか」と思うかもしれない。しかし、フロントエンドのレンダリングパイプラインやReactなどの仮想DOM差分検出において、この「意図しないミューテーション(破壊的変更)」はパフォーマンスの殺人鬼となる。

参照の共有による「サイレントバグ」

複数のコンポーネントやモジュール間で同じオブジェクトの参照が共有されている場合、一方がプロパティを書き換えると、他方のデータも瞬時に書き換わる。

const globalState = { theme: ‘light’ };

function Header({ state }) {
// 意図せずグローバルな状態を書き換えてしまう(サイドエフェクト)
state.theme = ‘dark’;
}

この変更は追跡が極めて難しく、「なぜか画面の描画がおかしい」というバグを生む。また、Reactなどのフレームワークでは、オブジェクトの参照アドレスが変わらない(=ミューテーションが行われた)場合、`Object.is` による浅い比較(Shallow Comparison)が変更を検知できず、UIの再描画がスキップされるという致命的な不具合につながる。

—

3. 実務で即戦力となる「堅牢な設計パターン」

では、私たちはどうコードを書くべきか。実務の現場ですぐに応用できる、美しく保守性の高いコードパターンを提示する。

パターンA:オブジェクトのイミュータブルな更新(Modern JavaScript)

オブジェクトや配列を更新する際は、元のオブジェクトを直接書き換えるのではなく、スプレッド構文(`…`)を用いて「新しいオブジェクトを生成してスタック上のポインタを付け替える」のがモダンJSの鉄則だ。

/