【実務・中級編】 constで宣言したオブジェクトの変更 – JavaScript実践ガイド

なぜ `const` なのに書き換えられるのか? JavaScriptの「参照」という残酷な現実

現場でコードレビューをしていると、若手エンジニアから「`const`で宣言したはずのオブジェクトが書き換わってバグりました!」という悲鳴をたまに聞く。そのたびに僕は、「`const`は『変数の再代入』を禁止しているだけで、『中身の不変性』を保証しているわけじゃないんだよ」と説明する。

これ、JavaScriptの仕様を深く理解していないと、意外と見落としがちな落とし穴なんだ。今日は、なぜそんな仕様になっているのか、そして実務で「本当に変更させない」ためにはどうすればいいのか、深掘りしていこう。

1. メモリの住所を固定しているだけという真実

まず、JavaScriptエンジンがどうメモリを扱っているかをイメージしてほしい。`const user = { name: ‘Alice’ }` と書いたとき、エンジンは以下の2つのことを行っている。

1. メモリ上のどこかに `{ name: ‘Alice’ }` というオブジェクトを生成する。
2. `user` という変数に、そのオブジェクトが格納されている「メモリの番地(参照)」を書き込む。

`const` が保証しているのは、この「番地(参照先)」を他の場所に変更させないことだ。決して、「番地の先にある中身(プロパティ)」まで守ってくれるわけじゃない。だから、`user.name = ‘Bob’` とやっても、`user` が指し示している番地は変わらないから、JavaScript的には何の問題もないんだ。

これが、`const` でオブジェクトや配列の中身が書き換え可能な理由だ。

2. 「完全な不変性」を手に入れる:Object.freeze()の極意

もし君が、「この設定オブジェクトは絶対に誰にも触らせたくない!」という領域を作りたいなら、`Object.freeze()` を使うのが正解だ。

こいつを使うと、そのオブジェクトは「凍結」され、プロパティの追加・削除・変更が一切できなくなる。厳密に言えば、非厳格モードでは無視されるだけだが、厳格モード(’use strict’)ではエラーを投げてくれる。フロントエンド開発なら、必ず strict モードで書くのが鉄則だよね。

‘use strict’;

const config = Object.freeze({
apiEndpoint: ‘https://api.example.com’,
timeout: 5000
});

// 凍結されたオブジェクトを変更しようとするとエラーが発生する
try {
config.timeout = 10000;
} catch (e) {
console.error(‘おいおい、凍結されているから変更できないぞ!’, e.message);
}

console.log(config.timeout); // 5000のまま

3. 注意! `Object.freeze()` は「シャロー(浅い)」コピーだ

ここで一つ、現場でよくある失敗談を共有しておくよ。`Object.freeze()` は、オブジェクトの直下のプロパティしか凍結しない。入れ子構造になった深いオブジェクトまでは守ってくれないんだ。

const user = Object.freeze({
name: ‘Alice’,
settings: { theme: ‘dark’ }
});

// 直下のプロパティは変更できない
user.name = ‘Bob’; // 失敗する

// しかし!入れ子のオブジェクトは凍結されていないので変更できてしまう(悲劇)
user.settings.theme = ‘light’;
console.log(user.settings.theme); // ‘light’ になってしまう!

この「シャロー凍結」にハマって、バグを量産するエンジニアは多い。もし深い階層まで完全に守りたいなら、再帰的に凍結する関数(Deep Freeze)を作る必要がある。

/

  • オブジェクトを深くまで再帰的に凍結する実用的なユーティリティ

/
function deepFreeze(obj) {
Object.keys(obj).forEach(prop => {
if (typeof obj[prop] === ‘object’ && obj[prop] !== null && !Object.isFrozen(obj[prop])) {
deepFreeze(obj[prop]);
}
});
return Object.freeze(obj);
}

const secureConfig = deepFreeze({
auth: { token: ‘secret’ }
});

secureConfig.auth.token = ‘hacked’; // 変更できない!
console.log(secureConfig.auth.token); // ‘secret’

まとめ:結局、どう向き合うべきか?

実務の現場で重要なのは「何でも凍結すればいい」というわけではないということだ。

  • 小規模な設定値や定数: `Object.freeze()` で十分。
  • 状態管理(ReduxやReactのstateなど): そもそもミューテーション(直接変更)を避ける設計(イミュータブルな更新)を徹底すべき。`Object.freeze()` に頼らず、`…spread` 演算子で新しいオブジェクトを作って差し替えるのが、モダンなフロントエンドの流儀だ。

JavaScriptは柔軟性が高い分、こういった「お作法」を自分たちでコントロールする必要がある。`const` の意味を正しく理解し、必要に応じて凍結や不変な更新パターンを選択する。これができるだけで、君の書くコードの信頼性は一段階上のレベルへ到達するはずだよ。

何かあればいつでも聞いてくれ。現場からは以上だ。

コメント

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