【実務・中級編】 varによる変数宣言と関数スコープ – JavaScript実践ガイド

なぜ今さら `var` なのか?――現場の「技術的負債」を読み解く作法

フロントエンド開発の現場で、レガシーなコードベースをいじっているとき、ふと目に飛び込んでくる `var` の文字。「あぁ、またか」とため息をついたことはないだろうか?

現代のモダンなJavaScript開発において `var` は「避けるべき遺物」とされている。しかし、単に「使うな」と覚えるだけでは不十分だ。なぜ `var` が長年JavaScriptの足を引っ張ってきたのか、そしてブラウザが裏側でどうやってこの「厄介者」を処理しているのかを理解することは、君がシニアエンジニアへとステップアップするための重要な試金石になる。

今日は、あえて深淵を覗き込み、`var` の正体を論理的に解剖していこう。

—

1. `var` が引き起こす「関数スコープ」の罠

JavaScriptの `var` は、ES6で導入された `let` や `const` とは異なり、「関数スコープ」を持つ。これが諸悪の根源だ。ブロック(`if`文や `for`ループなど)を突き抜けて、一番近い関数全体まで突き抜けてしまう。

function legacyLogic() {
if (true) {
var shadow = “私はブロックを突き抜けます”;
}
// ブロックの外からでも参照できてしまう
console.log(shadow); // -> “私はブロックを突き抜けます”
}

legacyLogic();

これの何が問題か? 大規模なアプリケーションになると、意図しない場所で変数が書き換えられたり、メモリリークの温床になったりする。特に非同期処理と組み合わせたときの `var` の挙動は、まさにバグの温床だ。

—

2. 「巻き上げ(Hoisting)」という名のブラウザの裏側

`var` を語る上で避けて通れないのが「巻き上げ」だ。ブラウザのエンジン(V8など)は、コードを実行する前に「コンパイル」に近いフェーズ(メモリ確保)を行う。この時、`var` で宣言された変数はスコープの先頭に「引き上げられ」、`undefined` で初期化される。

console.log(myVar); // エラーにはならず、undefined が出力される
var myVar = “Hello, World!”;

/

  • ブラウザが裏側でやっていることのイメージ:
  • var myVar; // 宣言だけ先にメモリに確保される(巻き上げ)
  • console.log(myVar); // 初期値の undefined が表示される
  • myVar = “Hello, World!”;

/

`let` や `const` は「一時的なデッドゾーン(TDZ)」に守られているため、宣言前にアクセスすると `ReferenceError` を投げてくれる。`var` はエラーを吐かずに `undefined` を返すため、バグが表面化しにくく、調査を難航させるのだ。

—

3. 再宣言という名の「カオス」

`var` は同じスコープ内であれば、何度でも再宣言できてしまう。これは「コードの意図」を破壊する行為だ。

var user = “Admin”;
// …長いコードが続く…
var user = “Guest”; // 上書きしても警告すら出ない

console.log(user); // -> “Guest” (元の Admin はどこへ消えたのか?)

これが中規模以上のプロジェクトで起きると、デバッグは地獄と化す。`const` を使っていれば、再代入の試みは `TypeError` として即座に可視化される。「エラーを早く見つけること」は、エンジニアにとって最もコストパフォーマンスの高い防衛策だ。

—

実践的ベストプラクティス:明日からこう書け

チームのコードベースを改善するために、以下のルールを徹底しよう。

1. `var` は存在しないものとして扱う: プロジェクトのESLint設定で `no-var` ルールを強制しよう。
2. 基本は `const`: 再代入の必要がない変数はすべて `const` にする。意図が明確になる。
3. 変更が必要なら `let`: `for`ループのカウンタなど、どうしても変更が必要な場合のみ `let` を使う。

綺麗なコードのための比較サンプル

// 【悪い例】var を使った不安定なループ処理
for (var i = 0; i < 3; i++) { setTimeout(() => console.log(‘var result:’, i), 100);
}
// -> 3, 3, 3 が出力される(iは関数スコープなので、ループが終わった時の値を参照する)

// 【良い例】let を使ったスコープの隔離
for (let j = 0; j < 3; j++) { setTimeout(() => console.log(‘let result:’, j), 100);
}
// -> 0, 1, 2 が出力される(letはブロックごとに新しいスコープを作る)

—

最後に:シニアとしての一言

`var` を理解することは、「JavaScriptがいかにして進化してきたか」という歴史を理解することに他ならない。しかし、過去の仕様に引きずられる必要はない。

「なぜ `let` や `const` が生まれたのか?」という問いを常に持ち、言語が提供する安全機構(セーフティネット)を最大限に活用すること。それが、君のような中級エンジニアが、プロダクトを堅牢に保つために果たすべき責任だ。

現場で `var` を見つけたら、それは「リファクタリングの絶好の機会」だと思ってほしい。一つひとつモダンに書き換えていくプロセスこそが、チームの品質を底上げする一番の近道だからね。

コメント

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