【入門編】関数スコープとブロックレベルスコープの境界線:if文やswitch文における変数の生存期間 – JavaScriptコア文法・モダン言語仕様と変数・関数解析バイブル

こんにちは!フロントエンドからNode.jsの内部挙動まで、日々JavaScriptと向き合っているエンジニアの皆さん。

JavaScriptを書き始めた頃、「あれ、さっき宣言したはずの変数が見つからない?」「`var`と`let`って何がそんなに違うの?」と戸惑った経験はありませんか?

他のプログラミング言語(JavaやC#など)からやってきた方だと、「ブロックの中で宣言したんだから、そのブロックを出たら変数は消えるはず」と思いますよね。でも、JavaScriptの歴史やエンジンの仕組みを紐解くと、そこには少しだけユニークなドラマがあるんです。

今回は、JavaScriptにおける「関数スコープとブロックレベルスコープの境界線」をテーマに、`var`、`let`、そして`const`がメモリ上でどう生きているのか、その生存期間の秘密を優しく、かつ深く紐解いていきましょう。ここをクリアすれば、JavaScriptの変数管理はバッチリマスターできますよ!

—

1. 変数の生存期間(ライフサイクル)とは何か?

私たちがコード中で `let x = 10;` と書いた瞬間、JavaScriptのランタイム(ブラウザのV8エンジンなど)は、メモリ上にその変数のための「席」を確保します。これがメモリの割り当て(Allocation)です。

そして、その変数が役目を終え、どこからも参照されなくなったとき、V8エンジンの「ガベージコレクタ(GC)」というお掃除ロボットがやってきて、そのメモリを回収します。これがメモリの解放(Deallocation / Liberation)です。

変数が「いつ生まれ、どこまで生き残り、いつ消えるのか」。この生存期間を決めるルールがスコープ(有効範囲)です。

—

2. 昔のJavaScriptの常識:`var` と「関数スコープ」

まずは、少し懐かしい(そして現在でもバグの温床になりやすい)`var` の世界を見てみましょう。

`var` は、ブロック(`{}`)の存在を無視します。`var` が認識するのは「関数」の壁だけです。これを関数スコープと呼びます。

以下のコードを見てください。

function checkVarScope() {
if (true) {
var oldSchool = “私は var です”;
}

// if文のブロックの外なのに…アクセスできてしまう!
console.log(oldSchool); // “私は var です” と出力される
}

checkVarScope();

「えっ、`if` 文のブロックの中で宣言したのに、外側から読めちゃうの?」と驚きましたか? そうなんです。`var` にとっては、`if` 文の `{}` はただの飾り。関数の中であれば、どこで宣言してもその関数のトップに「巻き上げ(Hoisting)」られ、関数全体からアクセスできるようになってしまうのです。

これが何を意味するかというと、「変数の生存期間が無駄に長くなる」ということです。関数が実行されている間、`oldSchool` はずっとメモリの領域を占有し続けます。

—

3. モダンJSの主役:`let` / `const` と「ブロックレベルスコープ」

私たちが現代のJavaScriptで安心してコードを書けるのは、ES2015 (ES6) で導入された `let` と `const`、そしてブロックレベルスコープのおかげです。

`let` や `const` は、`if` 文や `for` 文、あるいはただの `{}`(ブロック)さえも、厳格な「壁」として認識します。

function checkLetScope() {
if (true) {
let modern = “私は let です”;
const constant = “私は const です”;
console.log(modern); // ここはもちろんOK
}

// ブロックの外からアクセスしようとすると…?
// console.log(modern); // ReferenceError: modern is not defined
}

checkLetScope();

`if` 文のブロックを抜けた瞬間、`modern` や `constant` はスコープ外(生存期間の終了)となり、V8エンジンのメモリ空間から速やかに消え去る対象(正確には、実行コンテキストのポップに伴うレキシカル環境の破棄)となります。

メモリとパフォーマンスの観点から

「変数が早く消える」ということは、それだけメモリのフットプリント(占有量)が小さくなり、ガベージコレクタの負担も減るということです。モダンな開発で `var` を使わず、極力 `const`(次点で `let`)を使うべきなのは、文法上の安全性だけでなく、ランタイムのメモリ効率の面でも理にかなっているからなのです。

—

4. デベロッパーツールで「生存期間」の境界線を覗き見する

では、ブラウザのデベロッパーツール(Chrome DevToolsなど)を使って、この変数の生存期間の境界線を脳内だけでなく、視覚的・実用的にイメージしてみましょう。

実際のメモリダンプ(Heap Snapshot)を取得するまでもなく、「Sources」タブの「Scope」ペインを見ると、今どの変数がどこに生きているかが一目瞭然です。

例えば、以下のようなコードをデバッグ実行したとします。

function debugMemory() {
let heavyData = “非常に大きな文字列データやオブジェクトのつもり…”;

if (true) {
let blockScopedVar = “ブロック内のローカル変数”;
debugger; // ← ここで処理を一時停止(ブレークポイント)
}

// この行にdebuggerを置くと、blockScopedVarは消えている!
debugger;
}

debugMemory();

1. 1回目の `debugger;`(ブロック内)で一時停止したとき

  • DevToolsの「Scope」パネルを見ると、`Local` スコープの中に `heavyData` と共に、ブロック固有のスコープ(Block)が生成され、そこに `blockScopedVar` が存在しているのが確認できます。

2. 2回目の `debugger;`(ブロック外)で一時停止したとき

  • ブロックの実行が終了したため、Scopeパネルから `blockScopedVar` を含むBlockスコープ自体が綺麗に消え去っています。

このように、スコープの境界線を跨いだ瞬間、JavaScriptエンジンは内部のレキシカル環境(Lexical Environment)の参照を切断し、メモリ解放への道を整えます。これが、私たちが意識せずとも裏側で行われているメモリ管理のメカニズムです。

—

5. 陥りがちな罠:`switch` 文におけるスコープの落とし穴

最後に、初学者が最もハマりやすい「境界線の罠」を一つご紹介しましょう。それは `switch` 文です。

`switch` 文は、一見すると複数の `case` というブロックに分かれているように見えますが、`switch` 全体で一つの大きなブロック(または関数内であれば関数スコープ)として扱われます。

function handleSwitch(type) {
switch (type) {
case ‘A’:
let message = “Aです”;
console.log(message);
break;
case ‘B’:
// あれっ? 同じスコープ内で同じ変数名を宣言しちゃった!
// let message = “Bです”; // SyntaxError: Identifier ‘message’ has already been declared
let messageB = “Bです”; // 別名にすればOK
console.log(messageB);
break;
}
}

`switch` 文の中では、`case ‘A’` の中で宣言した `let message` が、なんと `case ‘B’` の領域までスコープとして共有(あるいは重複宣言エラーの対象)になってしまいます。
`switch` 文の中でローカル変数を使いたい場合は、各 `case` の中に明示的に中括弧 `{}` を書いてブロックスコープを作ってあげるのが、V8エンジンにとっても人間にとってもクリーンで安全な書き方です。

function handleSwitchClean(type) {
switch (type) {
case ‘A’: {
let message = “Aです”; // このブロック内だけの生存期間
console.log(message);
break;
}
case ‘B’: {
let message = “Bです”; // こっちは別のブロックなので重複エラーにならない
console.log(message);
break;
}
}
}

—

まとめ

いかがでしたでしょうか?

  • `var` は関数スコープ。ブロックを無視して生き続けるため、メモリ管理やバグの観点から現代では原則NG。
  • `let` / `const` はブロックレベルスコープ。`if` 文や `{}` の外に出た瞬間、生存期間を終えてメモリから解放される(ガベージコレクションの対象になる)。
  • `switch` 文など、見た目に反してスコープが共有される特殊なケースでは、明示的なブロック `{}` で囲むことで安全な生存期間をデザインできる。

変数のスコープと生存期間を意識できるようになると、メモリリークを防げるだけでなく、「意図しない変数の上書き」に悩まされない堅牢なコードが書けるようになります。

ここをクリアしたあなたなら、もうJavaScriptの変数やメモリ管理の基礎はバッチリマスターできていますよ!自信を持って次のステップへ進んでくださいね。

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