こんにちは!フロントエンドからNode.jsのランタイム内部まで、JavaScriptの生態系をくまなく見渡してきたチーフアーキテクトの私です。
みなさんは日々のコーディングで、変数の宣言に何を使っていますか?「なんとなく `const` を使っているけれど、時々 `let` にして、古いコードやサンプルで見かける `var` もまあ動くからいいか……」なんて思っていませんか?
もしそうなら、ちょっと待ってください。その「なんとなく」の選択が、あなたのアプリケーションを予期せぬメモリの迷宮へと誘い、デバッグ地獄を引き起こす原因になっているかもしれません。特に `var` が持つ仕様の歪みは、JavaScriptという言語の歴史的なバグとも言える厄介な代物です。
ここをしっかりとクリアすれば、あなたのJavaScriptのコードは一気にモダンで堅牢なものに生まれ変わりますよ。今日は、なぜ現代のJavaScript開発において `var` が厳禁とされているのか、その核心に迫っていきましょう。
—
1. なぜ `var` は悪なのか? —— 関数スコープという「見えない壁」の正体
JavaScriptの歴史を少しだけ紐解くと、`var` はこの言語が誕生した当初から存在する「唯一の変数宣言」でした。当時の設計思想では、変数の有効範囲(スコープ)を区切るのは「関数」だけでした。これを「関数スコープ」と呼びます。
ここで、他の言語(C、Java、Pythonなど)からやってきた開発者が必ずと言っていいほどハマる罠があります。これらの言語では、`if`文や `for`文などのブロック `{}` がスコープを作りますよね? しかし、`var` はその常識を軽々と踏みにじります。
言葉だけだとイメージしにくいと思うので、具体的な「汚染実験」のコードを見てみましょう。
// 【実験1】varの関数スコープを突き抜ける性質
function runPollutionTest() {
if (true) {
var hazardousVariable = “私はvarで宣言された危険人物です”;
}
// ifブロックの外側なのに……アクセスできてしまう!
console.log(hazardousVariable);
}
runPollutionTest();
// 実行結果: “私はvarで宣言された危険人物です”
信じられますか? `hazardousVariable` は `if` という小さな部屋(ブロック)の中で宣言されたはずなのに、その外側の部屋(関数の中であればどこからでも)にダダ漏れになっています。これが、`var` が生み出す「スコープ汚染」の第一歩です。
さらに恐ろしいグローバル汚染
もし、この `var` が関数の中ですらなく、スクリプトのトップレベル(どこにも属さない場所)で使われたらどうなるでしょうか?
ブラウザ環境であれば `window` オブジェクト、Node.js環境であれば `global` オブジェクトという、すべての親玉である「グローバルオブジェクト」のプロパティとして強制的に登録されてしまいます。
// グローバル空間でのvarの暴挙
var globalPollution = “世界中を汚染するよ”;
// ブラウザなら window.globalPollution でアクセスできてしまう
console.log(window.globalPollution); // “世界中を汚染するよ”
他の人が書いたライブラリや、自分が別のファイルで書いた変数と名前がバッティングして、既存のデータを書き換えてしまう……。大規模開発において、これがどれほど恐ろしいバグの温床になるかは想像に難くないでしょう。
—
2. 巻き上げ(Hoisting)の罠:存在しないはずの変数が「undefined」になる怪奇現象
`var` を語る上で避けて通れないのが「巻き上げ(Hoisting)」という仕様です。
JavaScriptエンジン(V8など)は、コードを実行する前に、まずコード全体をスキャンして変数や関数の宣言をメモリ上に登録する「フェーズ(Creation Phase)」を挟みます。この時、`var` で宣言された変数は、宣言の場所がどこであれ、スコープの最上部に「巻き上げられ」、同時に初期値として `undefined` が代入されるという挙動を示します。
これの何が問題か、コードで確認してみましょう。
function checkHoisting() {
// え? 宣言する前に使っているのにエラーにならない!?
console.log(userCount); // 出力結果: undefined (エラーにならないのが一番タチが悪い)
// 延々とコードを読み進めた、はるか下の方で宣言している
var userCount = 100;
console.log(userCount); // 出力結果: 100
}
checkHoisting();
初学者のうちは、「エラーにならないなら親切じゃないか」と思うかもしれません。しかし、これはプログラミングにおいて最悪の仕様です。
存在しない(初期化されていない)はずの変数にアクセスしてもエラー(ReferenceError)にならず、勝手に `undefined` という「値があるんだかないんだか分からない状態」で処理が進んでしまうため、バグの発見が極端に遅れる原因になります。
—
3. 救世主登場:`let` と `const` による「ブロックスコープ」の恩恵
こうした `var` の負の遺産を完全に葬り去るために、2015年の近代JavaScript規格(ES2015 / ES6)で導入されたのが `let` と `const` です。
これらは、私たちが直感的に理解できる「ブロックスコープ」をもたらしてくれました。ブロック `{}` が現れたら、そこが変数の生存限界(壁)となります。
先ほどの `if` 文の例を `let` で書き直してみましょう。
// 【実験2】letによるブロックスコープの守り
function runSafeTest() {
if (true) {
let safeVariable = “私はletで守られた安全な変数です”;
console.log(safeVariable); // ブロック内なら読める
}
// ブロックの外からアクセスしようとすると……?
try {
console.log(safeVariable);
} catch (error) {
console.error(“キャッチしました: ” + error.message);
// 出力結果: キャッチしました: safeVariable is not defined
}
}
runSafeTest();
見事に見えない壁が機能し、ブロックの外からはアクセスできずにエラー(ReferenceError)になりました! 「変数がその生存すべき場所でのみ生きる」という、プログラミングの基本かつ鉄則が守られるようになったのです。
さらに、巻き上げの挙動も「安全」に
`let` や `const` も実は巻き上げ自体は行われますが、`var` のような自動的な `undefined` の初期化は行われません。宣言の行に到達する前にアクセスしようとすると、JavaScriptエンジンは容赦なくエラーを吐き出します。
この、変数が初期化されるまでの魔のゾーンは「一時的デッドゾーン(TDZ: Temporal Dead Zone)」と呼ばれています。
function checkTdz() {
// 宣言前にアクセスしようとする
try {
console.log(apiKey);
} catch (error) {
console.error(error.message);
// 出力結果: Cannot access ‘apiKey’ before initialization
// (初期化される前にアクセスすることはできません!)
}
let apiKey = “secret_12345”;
}
checkTdz();
エラーを出して即座に開発者にバグを知らせてくれる。これこそが、モダンな言語処理系が持つべき優しさであり、堅牢性です。
—
4. `let` と `const` の使い分けの極意
「じゃあ、これからは全部 `let` を使えばいいの?」と思ったそこのあなた。惜しいですが、もう一歩踏み込みましょう。
現代のJavaScript開発における黄金律はこれです。
> 「基本はすべて `const` で宣言し、再代入が必要な変数だけを `let` に格下げする」
`const` は「定数(値を変えられないもの)」を作るためのものだと思われがちですが、厳密には「一度代入した変数に、別の値を再代入することを禁止する(バインドを固定する)」ためのものです。
// 再代入不可(constの基本)
const API_URL = “https://api.example.com/v1”;
// API_URL = “https://api.malicious.com”; // エラー!勝手に書き換えられるのを防ぐ
// ただし、オブジェクトや配列の中身を変更(ミューテート)することは可能
const userConfig = { theme: “dark” };
userConfig.theme = “light”; // これはOK(参照先のアドレスは変わっていないため)
変数の再代入を厳格に禁止(イミュータブルに)することで、コードのどこで値が書き換わったのかを追う必要がなくなります。これにより、脳内メモリの負荷(認知負荷)が劇的に下がり、バグの起きない美しいコードベースを維持できるようになるのです。
—
まとめ:今日から `var` をコードから全滅させよう
いかがでしたでしょうか? 今回の重要ポイントをサクッとまとめます。
- `var` は関数スコープを無視して外側に漏れ出す(スコープ汚染)
- `var` は巻き上げ時に勝手に `undefined` で初期化され、バグに気づきにくい
- `let` と `const` はブロック `{}` という堅固な壁(ブロックスコープ)を作る
- 宣言前のアクセスを許さない(TDZ)ことで、エラーを早期に検知できる
- 基本は `const`、再代入が必要な時だけ `let` を使う
レガシーな解説書や古いネットの記事には、いまだに `var` を平然と使っているサンプルが散見されますが、現代のフロントエンド開発やNode.jsエコシステムにおいて、`var` を使う正当な理由は1ミリも存在しません。
今日この瞬間から、あなたのコードから `var` を完全に追放し、`const` と `let` によるクリーンで予測可能な世界へ足を踏み入れましょう。ここをクリアしたあなたなら、どんな複雑なアプリケーションの設計も怖くありませんよ。バッチリマスターしていきましょう!