こんにちは!フロントエンドからNode.jsの深層まで、日々のコードがV8エンジン上でどう息づいているかを見つめ続けているシニアアーキテクトです。
JavaScriptを学び始めると、変数を宣言する方法として `var`、`let`、`const` の3つに出会いますよね。「どれを使っても同じように値が入るなら、昔からある `var` でもいいのでは?」と思ってしまうかもしれません。
ですが、ここをクリアすれば、あなたの書くコードは「動くだけのコード」から「V8エンジンが最速で処理したくなる洗練されたコード」に生まれ変わります。今日は、なぜモダンなJavaScriptにおいて `var` が嫌われ、`let` や `const` を使わなければならないのか、その本質をV8エンジンの裏側の世界を覗きながら一緒に解き明かしていきましょう!
—
そもそも `var` とは何だったのか?
JavaScriptが誕生した初期の頃、変数を宣言する唯一の手段は `var` でした。しかし、この `var` には「スコープ(変数が有効な範囲)が広すぎる」という重大な仕様上の欠陥がありました。
まずは、よくある `var` の挙動を見てみましょう。
function checkScope() {
if (true) {
var legacyVar = “私はvarです”;
}
// ifブロックの外なのに、普通にアクセスできてしまう!
console.log(legacyVar); // 出力: 私はvarです
}
checkScope();
他のプログラミング言語(Java、C++、Pythonなど)を触ったことがある方なら、「えっ、`if` ブロックの中で宣言した変数なのに、ブロックの外から見えちゃうの?」と驚くはずです。
`var` は、ブロック(`{}`)単位ではなく、関数単位でしかスコープを作りません。そのため、関数の中であればどこからでもアクセスできる「スコープ汚染」が簡単に起きてしまうのです。
「巻き上げ(Hoisting)」という奇妙な挙動
さらに `var` を厄介にしているのが「巻き上げ」です。JavaScriptのコードは、実行される前にエンジンによって一度スキャンされ、変数の宣言が勝手にコードの先頭に持ち上げられます。
console.log(username); // エラーにならない! 代わりに「undefined」が出る
var username = “Taro”;
console.log(username); // Taro
これ、初学者のうちは本当に混乱しますよね。「宣言する前に使っているのにエラーにならない」というのは、バグの温床になりやすい非常にトリッキーな仕様です。
—
V8エンジンの視点:なぜ `var` は最適化の敵なのか?
ここからが少しディープで面白い話です。私たちが書いたJavaScriptのコードは、GoogleのJavaScriptエンジンである「V8」によって、実行時にマシン語(CPUが直接理解できる言葉)に翻訳されます。
V8エンジンは、コードを高速に実行するために「最適化(Optimization)」という魔法を使います。その中でも強力な武器が「インライン展開(Inlining)」や「スコープの静的解析(Static Scoping)」です。
1. 静的解析(Compile-time Analysis)の崩壊
現代のコンパイラは、コードを実行する前に「この変数がどこで宣言され、どこで消滅するか」を完全に把握(静的解析)したいのです。
- `const` や `let` の場合: ブロック(`{}`)単位でスコープが厳格に決まっているため、V8は「あ、この変数はこの短いブロックが終わったらメモリから解放していいんだな」と瞬時に判断できます。
- `var` の場合: 関数全体のどこからでもアクセスできるため、V8は変数のライフサイクル(寿命)をコンパイル時に確定できません。「もしかしたら、この後予期せぬ場所から書き換えられるかもしれない…」と余計な疑心暗鬼を生み、最適化のチャンスを逃してしまうのです。
2. ヒープメモリ(Heap Memory)への過剰な負担
変数のスコープが曖昧で寿命が長くなると、V8はその変数を高速な「スタック領域」ではなく、管理コストの高い「ヒープ領域」に配置せざるを得なくなります。
ヒープが増えると、メモリのゴミ掃除である「ガベージコレクション(GC)」の頻度が高くなり、結果としてブラウザがカクつく(フレームレートが落ちる)原因になります。
—
モダンな解決策:`let` と `const` を使おう
こうした `var` の抱える構造的な問題をすべて解決するために、ES2015(ES6)で導入されたのが `let` と `const` です。
これらを使うと、変数を使える範囲(スコープ)が `{}`(ブロック)単位に限定され、巻き上げによる予期せぬバグも「Temporal Dead Zone(一時的死空間)」という仕組みによってコンパイル段階でシャットアウトされます。
function safeScope() {
if (true) {
let modernLet = “私はletです”;
const modernConst = “私はconstです”;
console.log(modernLet); // 出力される
console.log(modernConst); // 出力される
}
// ブロックの外からアクセスしようとすると…?
// console.log(modernLet);
// ➔ ReferenceError: modernLet is not defined (エラーでしっかり守ってくれる!)
}
safeScope();
私たちが心掛けるべき黄金律
実務の現場では、以下のシンプルなルールを徹底するだけで、コードの品質とV8の最適化効率が劇的に跳ね上がります。
1. 基本はすべて `const` で宣言する(値の再代入が必要ない限り、変数は変えないのが鉄則)。
2. ループのカウンターや、後から値を変える必要がある場合のみ `let` を使う。
3. `var` は絶対にコードベースに書かない(Linterでエラーにする)。
—
まとめ
今回は、`var` がなぜV8エンジンの最適化を阻害し、私たちのコードを遅く・複雑にするのかを、コンパイルの視点から紐解いてみました。
- `var` は関数スコープを持ち、巻き上げやスコープ汚染を引き起こす。
- V8エンジンは変数の寿命を予測しにくくなり、 агрессивな(積極的な)最適化を行えなくなる。
- `let` と `const` を使うことで、スコープが明確になり、エンジンも私たちプログラマも安心してコードを最適化できる。
ここをクリアすれば、もうあなたの書くJavaScriptは初級者の域を脱しています。V8エンジンとも仲良くなれる、洗練されたモダンなコードライフを一緒に楽しんでいきましょう!