こんにちは!フロントエンドから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の基礎力は確実に次のステージへステップアップしていますよ。一緒にモダンで美しいコードを書く楽しさを極めていきましょう!