こんにちは!フロントエンドからNode.jsの深層まで、日夜JavaScriptと向き合っているあなたの先輩エンジニアです。
今回は、JavaScriptの基本中の基本でありながら、実はコードの品質と堅牢性を大きく左右する「変数の宣言(`const`の真価)」についてお話ししますね。
「変数を宣言するときは、とりあえず`let`を使っていませんか?」
「`const`は『再代入できない箱』くらいに思っていませんか?」
もちろん間違いではないのですが、ここをクリアして`const`が持つ設計思想を理解できるようになると、あなたの書くコードは見違えるほどバグに強く、読みやすいものに生まれ変わります。ここをマスターすれば、JavaScriptの基本はもうバッチリですよ。さあ、一緒に本質の世界へ踏み出しましょう!
—
1. なぜ「再代入の可否」だけで`const`を語ってはいけないのか
私たちが普段何気なく使っている`var`、`let`、そして`const`。それぞれの違いを簡単に振り返ってみましょう。
- `var`: 再代入可・再宣言可(スコープがガバガバなので、現代の開発ではもう封印すべき遺物です)
- `let`: 再代入可・再宣言不可(ブロックレベルスコープを持ちます)
- `const`: 再代入不可・再宣言不可
多くの入門書では、「値を変えないなら`const`、あとで変えるなら`let`を使おうね」と教えられます。間違いではありません。でも、それだけだと`const`の本質を見誤ってしまいます。
チーフアーキテクトとしての私の視点から言わせてもらうと、`const`とは「この変数の指し示すアドレス(参照先)は、この先一生ブレませんよ」という、コードの読み手に対する強い誓約(コントラクト)なんです。
脳内イメージ:変数の「矢印」を固定する
JavaScriptの変数は、メモリ上のデータへ向かう「矢印」のようなものです。
let name = “Alice”;
// 「name」という矢印は、最初は “Alice” を指しているが……
name = “Bob”;
// あとから “Bob” に矢印を向け替えることができる(let)
const version = “1.0.0”;
// 「version」という矢印は、一生 “1.0.0” を指し続ける!向け替えは絶対禁止(const)
`let`を使うということは、「この変数の矢印は、あとで別のところに向け替えられるかもしれないよ」とコードに宣言していることになります。つまり、コードの読み手は「この変数、この先の処理でどこ書き換わるんだっけ?」と、心のメモリを消費しながらコードを追う必要があるのです。
一方、最初から`const`で宣言されていれば、読み手は「あ、この値はコードの最後まで絶対に変わらないんだな」と安心して思考をスルー(またはキャッシュ)できます。 この「読み手の脳内認知負荷を下げる」ことこそが、`const`を多用すべき最大の理由なんです。
—
2. 陥りがちな罠:`const`は「不変(イミュータブル)」ではない?
ここで、JavaScript初学者が必ずと言っていいほどハマるポイントを解説しておきますね。
「`const`で宣言したのに、中身が書き換わっちゃったんだけど! バグなの?」という疑問です。
const user = {
name: “Alice”,
age: 25
};
// これはおっけー? それともエラー?
user.age = 26;
console.log(user.age); // 26 (あれ、書き換わった……?)
// でもこれはエラーになる
user = { name: “Bob”, age: 30 }; // TypeError: Assignment to constant variable.
そうなんです。`const`が制限しているのは、あくまで「変数への再代入(矢印の向け替え)」だけです。オブジェクトや配列の「中身(プロパティや要素)」を変更することは防げません(これをJavaScriptでは「ミュータブル(可変)」と呼びます)。
「じゃあ意味ないじゃん!」と思いましたか? いいえ、大ありです。
オブジェクト自体をまるっと別のものにすげ替えるようなバグ(例:設定オブジェクトを間違えて上書きしてしまう等)をエンジンレベルで確実に防ぎつつ、「中身のプロパティだけを安全に更新していく」というモダンな設計が可能になるからです。
—
3. 実務リファクタリング事例:宣言的プログラミングへの昇華
では、実際の現場でよくある「残念なコード」を、`const`と宣言的プログラミングの思想を使って美しくリファクタリングしてみましょう。
悪い例:`let`の乱用と状態の迷子
以下のコードを見てください。ユーザーの権限に応じて、割引後の価格を計算する処理です。
// 【アンチパターン】letだらけで、どこで値が変わるか追いかけにくい
let finalPrice = 0;
let basePrice = 10000;
let discountRate = 0;
if (user.isPremium) {
discountRate = 0.2;
} else if (user.isMember) {
discountRate = 0.1;
} else {
discountRate = 0.0;
}
// 途中で計算が挟まる
let tax = 1.1;
finalPrice = (basePrice (1 – discountRate)) tax;
console.log(finalPrice);
`let`で変数が初期化され、あちこちの条件分岐で値が書き換えられています。これ、コードが1,000行に膨れ上がったとき、デバッグ地獄になりますよね。「この`discountRate`、本当に意図した分岐を通ってきたっけ?」と不安になります。
良い例:`const`と三項演算子/即時評価による宣言的コード
これを、V8エンジンにとっても人間にとっても優しい「宣言的」なコードにリファクタリングします。
// 【ベストプラクティス】すべての変数がconst。データの流路が完全に一意に決まる
const BASE_PRICE = 10000;
const TAX_RATE = 1.1;
// 条件分岐を「値の決定」としてスッキリまとめる(即時評価や関数分離も有効)
const getDiscountRate = (isPremium, isMember) => {
if (isPremium) return 0.2;
if (isMember) return 0.1;
return 0.0;
};
const discountRate = getDiscountRate(user.isPremium, user.isMember);
// 計算結果も一度きりの代入(=const)で確定させる
const finalPrice = (BASE_PRICE (1 – discountRate)) TAX_RATE;
console.log(finalPrice);
どうでしょうか?
すべての変数が`const`で宣言されているため、「この変数は宣言された瞬間の値から一切変化しない」という絶対的な信頼が生まれます。変数の状態変化を脳内で追跡する必要がなくなるため、バグが入り込む隙が圧倒的に減るのです。
—
4. チーフアーキテクトからのメッセージ
JavaScriptを書くとき、私たちは無意識にV8エンジンのメモリ空間や、ブラウザのレンダリングスレッドと対話しています。
`let`を使うということは、ランタイムに対して「このメモリ領域は書き換えられる可能性があるよ」と伝えることであり、それは時にV8の最適化(インラインキャッシュなど)の妨げになることもあります。何より、人間の脳にとって「状態が変化しないこと(イミュータビリティ)」は、複雑なシステムをハックするための最大の武器です。
これからのコーディングでは、まず最初に「とりあえず`const`と書く」習慣をつけてみてください。どうしても再代入が必要になった時だけ、最小限のスコープで`let`に格上げする。
このアプローチを徹底するだけで、あなたの書くコードの質は一段も二段も跳ね上がります。
さあ、今日のコーディングからさっそく試してみてくださいね。JavaScriptのマスターへの道は、こうした小さな思考の積み重ねの先にありますよ!