こんにちは!フロントエンドからNode.jsの深層まで、日々のコードと向き合っているあなたへ。
今日はJavaScriptの変数宣言、特に「`let`と`const`をどう使い分けるべきか」というテーマについて、少しアーキテクチャの視点を交えながらお話ししていきますね。
プログラミングを始めたばかりの頃や、他の言語(JavaやPython、C#など)からやってくると、「とりあえず全部`let`で書いておけば動くし楽だな」と思いがちですよね。実際、それでもコードは動きます。しかし、大規模なアプリケーションや、チームでの開発が進むにつれて、「あれ、この変数、どこで書き換えられているんだっけ?」と迷子になってしまう原因を作ってしまうのも、この「とりあえず`let`」という習慣なんです。
ここをしっかりとクリアすれば、JavaScriptのコードの質がグッと上がり、バグの少ない堅牢な設計ができるようになりますよ。さあ、一緒に本質をマスターしていきましょう!
—
1. なぜ `var` は封印され、`let` と `const` が生まれたのか
私たちがモダンなJavaScriptを書くとき、`var`を使うことはもうほとんどありません。なぜなら、`var`には「変数のスコープが広すぎる(関数スコープ)」ことや、「意図しない場所で同じ名前の変数を再宣言できてしまう」という、ランタイム(V8エンジンなど)にとっても開発者にとっても優しくない仕様があったからです。
ES2015(ES6)で登場した `let` と `const` は、この問題を鮮やかに解決しました。
変数が生存できる範囲(スコープ)をブロック単位(`{}`の中)に閉じ込め、どこからでも書き換えられるという「不確実性」を排除するために作られたのです。
`let` と `const` の基本スタンス
- `const` (コンスタント / 定数): 「この変数は、一度代入したら二度と別の値やオブジェクトを再代入しない」という強い誓約をエンジンと開発者に告げる宣言です。
- `let` (レット): 「この変数は、処理の途中で値が変化する(再代入される)予定がある」ということを明示する宣言です。
—
2. アーキテクチャ視点での「使い分け」:コードの意図を語らせる
チーフアーキテクトとして、私がチームのコードレビューをする際、一番厳しく見るポイントの一つが「`let`が無駄に使われていないか」という点です。
実は、モダンなJavaScriptコードにおいて、変数の8割以上は `const` で宣言できるはずなんです。なぜなら、プログラムの中にあるデータの大部分は、「一度生成したら、別のものにすり替える必要がない」ものだからです。
「再代入」と「中身の変更」の決定的な違い
ここで初心者の人が一番ハマりやすい罠があります。それは、「`const`にしたら、オブジェクトの中身も変更できないんでしょ?」という誤解です。
実際にコードを見てみましょう。
// 【NGな例】すべてを let で宣言してしまう
let user = { name: ‘Taro’, age: 20 };
let score = 100;
// user の中身を変えたいだけなのに、letだから何でもできてしまう
user = { name: ‘Jiro’, age: 25 }; // これ、再代入です(オブジェクト自体のすり替え)
score = 120; // 再代入
上記のコードでは、`user` という箱そのものを別のオブジェクトにすり替えてしまっています。これでは「この変数が何を表しているのか」が途中で変わってしまい、コードの追跡が難しくなります。
正しくは、こう書きます。
// 【推奨される例】基本は const でガチッと固める
const user = { name: ‘Taro’, age: 20 };
const score = 100;
// オブジェクトの「プロパティ(中身)」を変更するのは、constであっても可能です!
user.age = 21; // OK!箱(変数)自体は変えていないが、中身のデータを更新している
// score = 120; // ❌ エラー! TypeError: Assignment to constant variable.
// scoreは数値というプリミティブ型なので、再代入は許されない(これが正しい挙動)
🧠 ここがV8エンジンの裏側:なぜ `const` が優れているのか
V8エンジンなどのJavaScriptエンジンは、変数が再代入されない(`const`である)と確信できると、内部のメモリ最適化をより効率的に行うことができます。
人間にとっても、「あ、この値は最後まで変わらないんだな」と一目で分かるため、脳内のメモリ(認知負荷)を消費せずにコードを読み進めることができるのです。
—
3. 実践!ライフサイクル設計のベストプラクティス
では、実際の開発現場でどのように `let` と `const` を使い分けていけばよいのでしょうか。具体的なユースケースを見てみましょう。
ユースケース A: ループ処理とカウンター(`let` の出番)
値が刻々と変化することが確実な場合、迷わず `let` を使います。代表例は、ループのカウンターや、累積計算を行う変数です。
// 配列の合計値を算出する処理
const numbers = [10, 20, 30, 40];
let total = 0; // ループのたびに値が更新されるため let を使用
for (let i = 0; i < numbers.length; i++) { total += numbers[i]; // 再代入が発生する } console.log(total); // 実行結果: 100
ユースケース B: 設定値、DOM要素、関数(`const` の出番)
一度取得したDOM要素や、APIのベースURL、関数などは、プログラムの実行中に勝手に変わっては困るものです。これらはすべて `const` で保護します。
// DOM要素の取得(画面上のボタンなど)
const submitButton = document.querySelector(‘#submit-btn’);
// 設定値や定数
const API_BASE_URL = ‘https://api.example.com/v1’;
// アロー関数を使った処理の定義も const が基本
const calculateTax = (price) => {
const TAX_RATE = 0.1; // 内部の定数
return price (1 + TAX_RATE);
};
—
4. 陥りやすい罠:一時変数(Temporary Variables)の誘惑
初学者のコードでよく見かけるのが、関数の冒頭でとりあえず `let` で変数をたくさん宣言し、後から下の方で次々と値を代入していくスタイルです。
// 【避けるべきアンチパターン】
let result;
let status;
let message;
if (isSuccess) {
result = data.items;
status = 200;
message = ‘Success’;
} else {
result = [];
status = 400;
message = ‘Error’;
}
この書き方は、変数のスコープが広くなりすぎており、コードが長くなったときに「どこでどの値が入るのか」を追うのが非常に困難になります。
💡 アーキテクト流の改善策
三項演算子や、即時評価される関数(あるいは純粋な関数分割)を使い、変数を宣言と同時に `const` で初期化するのがモダンなアプローチです。
// 【スマートなモダンコード】
const { result, status, message } = isSuccess
? { result: data.items, status: 200, message: ‘Success’ }
: { result: [], status: 400, message: ‘Error’ };
これなら、`result` や `status` が途中で書き換えられる余地が一切なくなり、コードの安全性と可読性が劇的に向上しますよね。
—
まとめ:今日のゴールデンルール
ここまでの内容を、あなた自身の開発ですぐに使える「黄金律」としてまとめておきます。
1. デフォルトの宣言は常に `const` を使う。
2. ループのカウンターや、値の再代入がどうしても必要な場合だけ `let` を使う。
3. `var` は使わない(タイムトラベルバグの温床になるため)。
「迷ったらまずは `const`」。この意識を持つだけで、あなたの書くJavaScriptコードは驚くほど美しく、バグに強いものに生まれ変わります。
さあ、今日のコーディングからさっそく意識して試してみてください。あなたのJavaScriptマスターへの道順は、もうバッチリ整っていますよ!