「constは不変(Immutable)である」という誤解を解く――実務で生き残るためのJavaScriptスコープ論
フロントエンドの現場でコードレビューをしていると、未だに「`const`を使っているからこの変数は絶対に書き換わらない」と信じ込んでいる中堅エンジニアに出会うことがあります。
厳しいことを言うようですが、`const`は「値の不変(Immutability)」を保証する魔法の杖ではありません。
もしあなたが「`const`なら安全だ」と油断して大規模なデータ構造を扱っていれば、いつか必ず不可解なバグ(いわゆる「副作用の温床」)に足元をすくわれることになります。今回は、ブラウザのエンジンが裏側でどう動いているのかという視点を交えながら、現場で本当に使える`const`の作法を紐解いていきましょう。
—
1. `const`の正体は「バインディングの固定」
結論から言えば、`const`は「値」そのものを固定するのではなく、「変数名(識別子)とメモリ上のアドレス(参照先)の結びつき」を固定する命令です。
JavaScriptエンジン(V8など)は、変数宣言が行われると、メモリ内の特定の場所に値を割り当て、そのアドレスを変数名と紐付けます。`const`で宣言するということは、JSエンジンに対して「この変数名が指し示すアドレスを、二度と他の場所へ書き換えるな」と命じているに過ぎません。
参照先の固定と値の変更
以下のコードを見てください。なぜオブジェクトの中身は書き換わってしまうのか。
// ユーザー情報をオブジェクトで定義
const user = { name: “Alice”, role: “admin” };
// 【NG】これはエラーになります:変数userのアドレスを別の場所に変えようとしているため
// user = { name: “Bob”, role: “guest” };
// 【OK】これは通ります:オブジェクトのプロパティを操作しているだけで、
// userという変数が指し示している「メモリ上のオブジェクトの場所」は変わっていないからです。
user.name = “Bob”;
console.log(user); // { name: “Bob”, role: “admin” }
この挙動は仕様上の「参照の不変性(Reference Immutability)」です。`user`というラベルが、メモリ上の「あるオブジェクト」という住所を指し続けている限り、その中身(家の中)をリフォームすることは`const`の制約外なのです。
—
2. 「なぜ書き換わってしまうのか」への処方箋
実務において、この特性は時に諸刃の剣です。特にReactのような状態管理を伴うフレームワークでは、この「意図しないプロパティの書き換え」が、レンダリングの不整合やバグの温床になります。
もし、オブジェクトの中身すら一切触らせたくないという強固な意志があるなら、現代のJavaScriptには強力な武器があります。それが `Object.freeze()` です。
const config = Object.freeze({
apiEndpoint: “https://api.example.com”,
timeout: 5000
});
// Object.freezeをかけると、プロパティの変更も防げます(厳格モードではエラーになる)
config.timeout = 10000;
console.log(config.timeout); // 5000のまま(変更は無視される)
ただし、`Object.freeze`は「浅い凍結(Shallow Freeze)」です。ネストされたオブジェクトがある場合、その内側までは守れません。深くネストされたオブジェクトを扱うなら、`immer`のようなライブラリを使って「変更をイミュータブルに扱う」設計にするのが、今のフロントエンド開発の標準解です。
—
3. 実務で「constを愛する」ためのベストプラクティス
現場で読みやすい、かつ堅牢なコードを書くために、以下の指針を胸に刻んでください。
1. デフォルトは常に `const` を選ぶ
- `let` を使うのは、再代入が「設計上必須である場合(ループカウンタや蓄積変数など)」のみに限定してください。コードを読んだ瞬間に「あ、ここは後で値が変わるんだな」と脳が判断できるだけで、可読性は飛躍的に上がります。
2. 再代入を避ける設計にする
- `let` で宣言して後から値をpushするようなコードを書く前に、`.map()` や `.filter()`、あるいはスプレッド構文 `…` を使った「新しい配列・オブジェクトを作る」手法へ頭を切り替えましょう。
3. 変数のスコープを極小化する
- `const` はブロックスコープ(`{}` 内)に閉じるため、無駄に広いスコープで変数を定義しないこと。関数の先頭で全変数を宣言する古い書き方は捨ててください。
—
最後に:シニアエンジニアからのメッセージ
`const`を正しく使うということは、「このデータはどこで生成され、どこまで生存し、どこで終わるのか」というデータのライフサイクルを自分でコントロールするという意思表示です。
「とりあえず`const`でいいか」ではなく、「このデータは変更されるべきではないから`const`にするし、変更が必要ならそもそも別の構造にすべきではないか?」と一歩立ち止まって考える。その小さな思考の積み重ねこそが、バグの少ない、メンテナンス性の高いフロントエンドアーキテクチャへの唯一の近道です。
さあ、エディタを開いて、あなたのコードの中にある「なんとなくの`let`」を、一つずつ`const`に書き換えることから始めてみませんか?

コメント