閉じる

JavaScriptの変数スコープを理解する:var, let, constの使い分けと最新のベストプラクティス

JavaScriptにおける「スコープ」は、プログラムの堅牢性と保守性を左右する最も重要な概念の一つです。

2026年現在、JavaScript(ECMAScript)は進化を続け、新しい構文や機能が追加されていますが、変数が「どこで定義され、どこまで有効か」を定義するスコープの仕組みを正しく理解することは、依然としてエンジニアにとって必須のスキルです。

本記事では、初心者が陥りやすい罠から、モダン開発で求められる最新のベストプラクティスまでを詳しく掘り下げていきます。

スコープとは何か:プログラムの境界線

スコープ(Scope)とは、一言で言えば「変数や関数の有効範囲」のことです。

プログラム内の特定の場所から、ある変数にアクセスできるかどうかを決定するルールの集合体と言い換えることもできます。

JavaScriptには主に以下の4つのスコープが存在します。

  1. グローバルスコープ
  2. 関数スコープ
  3. ブロックスコープ
  4. モジュールスコープ

これらの境界線を意識せずにコードを書くと、意図しない場所で変数の値が書き換わってしまったり、メモリを無駄に消費してしまったりする原因となります。

グローバルスコープの特性とリスク

グローバルスコープとは、プログラムのどこからでもアクセス可能な最上位の領域です。

ブラウザ環境ではwindowオブジェクト、Node.js環境ではglobalオブジェクト、そしてモダンな環境では共通のglobalThisがこれに該当します。

JavaScript
// グローバルスコープで定義
const appVersion = "2.0.4";

function displayVersion() {
  // 関数の中からグローバル変数にアクセス可能
  console.log("Current Version: " + appVersion);
}

displayVersion();
実行結果
Current Version: 2.0.4

グローバル変数は便利に見えますが、現代の開発においてグローバル変数の多用は厳禁とされています。

理由は、どこからでも変更できてしまうため、コードの規模が大きくなった際に「どこで値が変わったのか」を追跡することが極めて困難になるからです。

これを「グローバル汚染」と呼びます。

関数スコープとvarの振る舞い

関数スコープとは、関数の中で定義された変数が、その関数内でのみ有効になる仕組みです。

かつてJavaScriptで主流だったvar命令は、この関数スコープに従います。

JavaScript
function exampleFunction() {
  var localValue = "Inside function";
  console.log(localValue);
}

exampleFunction();

// 関数の外からアクセスしようとするとエラー
try {
  console.log(localValue);
} catch (e) {
  console.log("Error: " + e.message);
}
実行結果
Inside function
Error: localValue is not defined

varには、後に説明する「巻き上げ(Hoisting)」という特殊な挙動があり、これがバグの温床となってきました。

そのため、2026年のモダン開発においてvarを新規に採用するケースはほぼありません

ES6以降の標準:ブロックスコープ

2015年のES6(ES2015)登場以降、JavaScriptの変数宣言はletconstが主役となりました。

これらが導入したのが「ブロックスコープ」です。

ブロックスコープとは

ブロックスコープとは、{}(波括弧)で囲まれた範囲内でのみ変数が有効になるスコープのことです。

if文やfor文などのブロックも対象となります。

JavaScript
if (true) {
  let blockScoped = "I am visible only here";
  console.log(blockScoped);
}

// ブロックの外からはアクセス不可
try {
  console.log(blockScoped);
} catch (e) {
  console.log("Error: " + e.message);
}
実行結果
I am visible only here
Error: blockScoped is not defined

この仕組みにより、ループ変数などが外部に漏れ出す心配がなくなり、より安全なプログラミングが可能になりました。

varとletの決定的な違い

以下の表は、varlet(およびconst)の主な違いをまとめたものです。

特徴varlet / const
有効範囲関数スコープブロックスコープ
再宣言可能不可能
再代入可能letは可能、constは不可
巻き上げあり(undefinedで初期化)あり(初期化前はアクセス不可)
グローバルオブジェクトへの登録ブラウザではwindowのプロパティになるならない

特に、同じ名前の変数を二度宣言できてしまうvarの性質は、大規模開発では致命的なミスに繋がります。

モダンなプロジェクトでは常にletまたはconstを選択するのが鉄則です。

巻き上げ(Hoisting)のメカニズム

JavaScriptには、変数の宣言がスコープの先頭に「引き上げられた」かのように振る舞う巻き上げ(Hoisting)という仕組みがあります。

varによる巻き上げ

varで宣言された変数は、宣言より前で参照してもエラーにならず、undefinedが返されます。

JavaScript
console.log(hoistedVar); // エラーにならず undefined
var hoistedVar = "Hello";
実行結果
undefined

これは、インタープリタがコードを実行する前に宣言部分をスキャンし、メモリ上に確保するためです。

let/constと一時的死角(TDZ)

一方、letconstも内部的には巻き上げが発生していますが、宣言より前でアクセスするとエラーになります。このアクセスできない期間を一時的死角(Temporal Dead Zone: TDZ)と呼びます。

JavaScript
try {
  console.log(tdzVar); // ReferenceErrorが発生
  let tdzVar = "World";
} catch (e) {
  console.log("Error: " + e.message);
}
実行結果
Error: Cannot access 'tdzVar' before initialization

TDZのおかげで、エンジニアは変数が定義される前にそれを使用するという不自然なコードを未然に防ぐことができます。

レキシカルスコープとクロージャ

JavaScriptのスコープを語る上で欠かせないのが「レキシカルスコープ(静的スコープ)」の概念です。

レキシカルスコープ

レキシカルスコープとは、関数を「どこで実行したか」ではなく、「どこで定義したか」によってスコープが決まる仕組みです。

JavaScript
const x = "global";

function first() {
  const x = "local";
  second();
}

function second() {
  console.log(x);
}

first();
実行結果
global

関数secondはグローバルスコープで定義されているため、たとえfirstの中から呼び出されたとしても、参照するのはグローバルなxとなります。

クロージャ:状態を保持するスコープ

このレキシカルスコープの性質を利用して、関数が定義された時の環境を保持し続ける仕組みが「クロージャ(Closure)」です。

JavaScript
function createCounter() {
  let count = 0; // 外部からは直接触れないプライベート変数

  return function() {
    count++;
    return count;
  };
}

const counter = createCounter();
console.log(counter());
console.log(counter());
実行結果
1
2

createCounterが実行を終えた後も、返された匿名関数は変数countへの参照を持ち続けます。

これにより、データのカプセル化(隠蔽)が可能になります。

2026年における最新のベストプラクティス

現代のJavaScript開発(TypeScript含む)において、スコープを安全かつ効率的に管理するための指針を紹介します。

1. constをデフォルトにする

変数を宣言する際、まずはconstを使うことを検討してください。

再代入が不要な変数を固定することで、プログラムの副作用を最小限に抑え、可読性を高めることができます。

どうしても値の更新が必要な場合のみ、letを使用します。

2. シャドウイング(Shadowing)を避ける

外側のスコープと同じ名前の変数名を、内側のスコープで定義することを「シャドウイング」と呼びます。

JavaScript
const name = "Global";

function hello() {
  const name = "Local"; // 外側のnameを隠してしまう
  console.log(name);
}

これは文法的に正当ですが、コードの可読性を著しく低下させ、誤修正を招く原因になります。

ESLintなどのリンターを導入し、シャドウイングを禁止または警告するように設定するのが一般的です。

3. プライベートクラスフィールドの活用

2026年の環境では、クラス内でのスコープ管理に「プライベートクラスフィールド(#記法)」が広く浸透しています。

JavaScript
class User {
  #id; // クラス外からは絶対にアクセスできない

  constructor(id) {
    this.#id = id;
  }

  getId() {
    return this.#id;
  }
}

const user = new User(101);
console.log(user.getId());
// console.log(user.#id); // SyntaxErrorになる
実行結果
101

従来の「慣習的にアンダースコア(_)を付ける」だけの疑似的な隠蔽ではなく、言語レベルで強固なカプセル化が提供されています。

4. モジュールスコープの徹底

ファイル単位でスコープが分離されるESモジュール(ESM)は、現代開発の標準です。

HTMLに直接スクリプトを書くのではなく、常にtype="module"を使用、あるいはビルドツールを介してファイルを分離することで、意図しないグローバル汚染を完全に回避しましょう。

まとめ

JavaScriptの変数スコープは、単なる文法のルールではなく、プログラムの安全性と予測可能性を担保するための土台です。

  • varの使用は控え、constletによるブロックスコープを基本とする。
  • 巻き上げTDZの挙動を理解し、初期化前の変数アクセスを防ぐ。
  • クロージャを理解して、不必要なグローバル変数を減らす。
  • クラスではプライベートフィールド(#)を活用し、情報の隠蔽を徹底する。

これらの基本を忠実に守ることで、2026年以降の複雑化するWebアプリケーション開発においても、バグの少ない、メンテナンス性の高いコードを記述できるようになります。

常に「この変数の寿命はどこまでか」を意識しながら、日々のコーディングに取り組んでみてください。

URLをコピーしました!