JavaScript: JavaScript スコープ

最終更新:2026-10-13

スコープとは、変数がどこでアクセスできるかを決める仕組みです。身分証明書のようなものだと考えてください。市区町村の証明書はその都道府県内でしか通用せず、都道府県の証明書は国外では役に立ちません。変数も同じで、スコープを出るとアクセスできなくなります。

関連する文:const let var for

1. スコープの種類とルール

var、let、const で最も外側のレベルで宣言された変数はグローバルスコープを持ち、どこからでもアクセスできます。

HTML
<script>
var globalVar = "私はグローバル変数です";
function test() {
  console.log(globalVar); // アクセス可能
}
test();
</script>

グローバル変数は便利ですが、名前空間を汚染しやすいです。プロジェクトが大きくなるにつれて、名前の衝突が避けられなくなります。グローバル変数の使用は最小限に抑えましょう。

(2) 関数スコープ

var で宣言された変数は関数スコープに従います。現在の関数内にのみ存在し、関数の外では消滅します。

HTML
<script>
function greet() {
  var message = "こんにちは";
  console.log(message); // 正常に動作
}
greet();
try {
  console.log(message); // ReferenceError!
} catch(e) {
  console.log("アクセス失敗: " + e.message);
}
</script>

var の関数スコープに関する典型的な落とし穴として、for ループで var を使うと、ループ終了後に変数が「漏れ出す」問題があります。

(3) ブロックスコープ

let と const で宣言された変数はブロックスコープに従います。現在の {} 内にのみ存在します。

HTML
<script>
if (true) {
  let x = 10;
  const y = 20;
  var z = 30;
  console.log("ブロック内: x=" + x + ", y=" + y + ", z=" + z);
}
console.log("ブロック外: z=" + z); // var はブロックに制限されない
try {
  console.log(x); // x にはアクセスできない
} catch(e) {
  console.log("x にアクセスできません: " + e.message);
}
</script>

これが、ループカウンターに var の代わりに let を使うべき理由です。

(4) スコープチェーン

内側のスコープは外側のスコープの変数にアクセスできます。学校で家のものを使うのと同じです(持っていさえすれば)。変数を探索する際、JavaScript は現在のスコープから外側に向かって層ごとに検索し、グローバルスコープに到達するまで続けます。この連鎖をスコープチェーンと呼びます。

HTML
<script>
const city = "東京";
function outer() {
  const district = "渋谷区";
  function inner() {
    const street = "神宮前";
    console.log(street, district, city); // すべてアクセス可能
  }
  inner();
}
outer();
</script>

外側のスコープは内側のスコープの変数にアクセスできません。教室の外から生徒のポケットの中身を取り出すことはできないのと同じです。

(5) ホイスティング

var の宣言はスコープの先頭に「持ち上げられ」ますが、代入は持ち上げられません。

HTML
<script>
console.log(a); // undefined(エラーにはならない!)
var a = 5;
// 実際の実行順序: var a; → console.log(a); → a = 5;
</script>

let と const もホイスティングされますが、宣言前にアクセスするとエラーがスローされます。この領域はテンポラルデッドゾーン(TDZ)と呼ばれます。

HTML
<script>
try {
  console.log(b); // ReferenceError!
} catch(e) {
  console.log("テンポラルデッドゾーン: " + e.message);
}
let b = 5;
</script>

結論:常に let と const を使い、var のホイスティングの落とし穴を避けましょう。

(6) クロージャの直感的な説明

クロージャ = 関数 + その関数が生まれた変数環境。故郷を離れた人が故郷の風景を覚えているのに似ています。故郷が後から変わっても、その人の記憶は変わりません。

HTML
<script>
function createCounter() {
  let count = 0;        // この変数はクロージャ内に「ロック」される
  return function() {
    count++;
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// count 変数は破壊されない。返された関数がまだ「覚えている」ため
</script>

createCounter はすでに実行を終了しています。論理的には count は破壊されるべきです。しかし、返された関数が count をしっかり保持しているため、生き残ります。これがクロージャです。

(7) クロージャの実用的なユースケース


(8) サンプル:ループにおける var と let の比較

▶ 試してみよう

(9) サンプル:スコープチェーンのデモ

▶ 試してみよう

(10) サンプル:プライベート変数を使ったクロージャカウンター

▶ 試してみよう

(11) サンプル:ホイスティングの比較

▶ 試してみよう

関連する文:const let var for

❓ よくある質問

Q for ループで var を使うと、すべてのコールバックが同じ値を出力するのはなぜですか?
A var は関数スコープを持つため、ループ終了後には i が1つしかなく、すべてのコールバックがそれを共有します(その時点ではループの最終値になっています)。let の場合、各反復で独立した i のコピーが作成されます。
Q クロージャはメモリリークを引き起こしますか?
A クロージャ自体はメモリリークではありません。意図的に変数への参照を維持する仕組みです。本当のリークは、クロージャがもう不要なのに別の参照チェーンから保持され続け、変数のガベージコレクションが妨げられる場合に発生します。対処法は、参照を null に設定することです。
Q let と const のどちらを使うべきですか?
A デフォルトでは const を使い、再代入が必要な場合のみ let を使ってください。この習慣により、変更すべきでない変数を誤って変更するのを防げます。

📖 まとめ

📝 練習問題

  1. makeGreeter(greeting) 関数を書きましょう。名前を引数に取り、greeting + "、" + name を返す関数を返してください。異なる挨拶で2つのグリーティング関数を作成し、互いに干渉しないことを確認してください。
  2. createWallet(initialBalance) 関数を書きましょう。deposit、withdraw、getBalance の3つのメソッドを返してください。balance は外側から直接アクセスできない(プライベート変数)ようにし、これらのメソッドを通じてのみ操作できるようにしてください。
  3. 以下のコードの出力とその理由を説明してください:
HTML
<script>
for (var i = 0; i < 3; i++) {
  setTimeout(function() { console.log(i); }, 100);
}
</script>

その後、0、1、2 と出力されるように修正してください。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%