JavaScriptのスコープ(グローバル、ローカル)

2025-07-28

スコープの基本概念

スコープとは、変数や関数がアクセス可能な範囲を決定する仕組みです。JavaScriptには主に以下のスコープがあります:

  • グローバルスコープ:コードのどこからでもアクセス可能
  • ローカルスコープ:特定の範囲内でのみアクセス可能
  • 関数スコープ
  • ブロックスコープ(ES6以降)

グローバルスコープ

グローバルスコープとは、プログラム全体からアクセスできる変数や関数の範囲のことです。たとえば、関数の外で宣言した変数はグローバルスコープを持ち、どこからでも使えます。ただし、グローバル変数を増やしすぎるとバグの原因になるので注意が必要です。

グローバル変数の定義

// グローバル変数
const globalVar = 'グローバル変数';

function checkGlobal() {
  console.log(globalVar); // アクセス可能
}

checkGlobal();
console.log(globalVar); // アクセス可能

注意点

  • グローバル変数はどこからでも変更可能
  • 名前衝突のリスクがある
  • 可能な限り避けるべき

意図しないグローバル変数

function createGlobal() {
  // var, let, constを付け忘れるとグローバル変数になる
  accidentalGlobal = '意図しないグローバル変数';
}

createGlobal();
console.log(accidentalGlobal); // アクセス可能(非推奨)

ローカルスコープ

ローカルスコープとは、ある特定の範囲(主に関数やブロック)の中だけで使える変数や関数の範囲のことです。例えば、関数内で宣言した変数はその関数の外からはアクセスできません。ローカルスコープは変数の衝突を防ぎ、安全にコードを書くために重要です。

関数スコープ

function showLocal() {
  // ローカル変数
  const localVar = 'ローカル変数';
  console.log(localVar); // アクセス可能
}

showLocal();
// console.log(localVar); // エラー(アクセス不可)

ブロックスコープ(ES6)

if (true) {
  // let/constはブロックスコープ
  let blockVar = 'ブロック変数';
  const BLOCK_CONST = 'ブロック定数';
  console.log(blockVar); // アクセス可能
}

// console.log(blockVar); // エラー(アクセス不可)
// console.log(BLOCK_CONST); // エラー(アクセス不可)

varの注意点とletの推奨

ローカルスコープでの var の注意点 は、var は 関数スコープ を持つため、ブロック {}(例えば if や for の中)ではスコープが限定されず、外側からもアクセスできてしまうことです。

if (true) {
  var functionScoped = '関数スコープ';
}
console.log(functionScoped); // アクセス可能(ブロックスコープではない)

これに対し、let や const はブロックスコープなので、ブロック外からはアクセスできず安全です。
varは意図しない変数の漏れや上書きを招きやすいため、基本はletやconstを使うのが推奨されます。

スコープチェーン

JavaScriptは変数を探す際、現在のスコープから外側のスコープへと順に探します。

const global = 'グローバル';

function outer() {
  const outerVar = '外側';

  function inner() {
    const innerVar = '内側';
    console.log(innerVar); // '内側'
    console.log(outerVar); // '外側'
    console.log(global);   // 'グローバル'
  }

  inner();
  // console.log(innerVar); // エラー(アクセス不可)
}

outer();

レキシカルスコープ(静的スコープ)

レキシカルスコープ(静的スコープ)とは、関数や変数の有効範囲(スコープ)が、コードを書いた場所(つまり「見た目」や「構造」)によって決まる仕組みのことです。

実行時ではなく、コードの階層(ネスト)構造を基にスコープが決まるので、関数がどこで定義されたかによってアクセスできる変数が決まります。

const name = 'グローバル';

function logName() {
  console.log(name);
}

function wrapper() {
  const name = 'ローカル';
  logName(); // 'グローバル'(定義時のスコープが使われる)
}

wrapper();

このコードでは、logName 関数はグローバルスコープで定義されているため、関数が呼ばれた場所(wrapper の中)ではなく、定義されたときのスコープ(グローバルスコープ)の変数 name を参照します。つまり、logName() はwrapper内のローカル変数 name ではなく、グローバルの name = ‘グローバル’ を出力します。

クロージャ

クロージャ(Closure)とは、関数とその関数が作られた時の環境(スコープ)をセットにして保持する仕組みのことです。

具体的には、ある関数の内部で定義された関数が、その外側の関数の変数にアクセスできる状態を指します。外側の関数の処理が終わっても、その変数が内部関数によって使い続けられるのが特徴です。

function outer() {
  let count = 0;

  return function inner() {
    count++;
    console.log(count);
  };
}

const counter = outer();
console.log(counter()); // 1
console.log(counter()); // 2

ここでinner関数は、outerの変数countを覚えていて、何度も使い続けています。これがクロージャです。クロージャはデータの隠蔽や状態の保持に便利で、JavaScriptの強力な機能の一つです。

即時実行関数式

即時実行関数式(IIFE:Immediately Invoked Function Expression)は、定義してすぐに実行される関数のことです。

// 基本構文
(function() {
  // ここにコードを書く
})();

// ES6のアロー関数で
(() => {
  // ここにコードを書く
})();

即時実行関数式をなぜ使うのか?(メリット)

  • 一度しか実行しないコードを書くときに便利
  • 変数のスコープを限定して、グローバル空間を汚さないため

JavaScriptで「スコープの分離」や「グローバル汚染の防止」によく使われます。

スコープのベストプラクティス

  1. グローバル汚染を避ける

グローバル変数を増やすと、同じ名前の変数が他の部分で上書きされたり、バグの原因になります。プログラム全体に影響を及ぼすため、問題が発見しにくくなります。なるべく変数は必要な範囲(ローカル)だけで使いましょう。

  • IIFE(即時実行関数式)を使用
  • モジュールパターンを採用
  1. constを優先

constは一度値を決めたら変えられないため、誤って変数を書き換えるミスを防げます。意図しない変更を防ぎ、コードの安全性と読みやすさが上がるので、基本はconstを使い、必要なときだけletを使います。

   // 👍 推奨
   const localVar = '値';

   // 👎 非推奨
   var localVar = '値';
  1. ブロックスコープを活用

letconstはブロック({}の中)だけ有効な変数を作れます。これにより、変数の影響範囲を狭くでき、同じ名前の変数が別の場所で混ざるのを防げます。バグを減らし、コードの管理が楽になります。

   // ループ内でlet/constを使用
   for (let i = 0; i < 5; i++) {
     // iはループ内のみ有効
   }
  1. 名前空間の使用

名前空間は、関連する変数や関数を1つのオブジェクトにまとめてグローバル変数を減らす方法です。これで「同じ名前の変数がぶつかる問題」を避けられ、コードの構造も整理しやすくなります。

   // グローバルを汚さない
   const MyApp = {};
   MyApp.utils = {};

スコープ関連の特殊なケース

ホイスティング(変数の巻き上げ)

ホイスティング(Hoisting)とは、JavaScriptがコードを実行する前に、変数や関数の宣言を自動的にコードの先頭に「持ち上げる(巻き上げる)」仕組みのことです。

特殊性のポイント

  • 関数宣言はまるごと先頭に移動して使えるので、宣言前でも呼び出せる。
  • var変数の宣言だけが巻き上げられ、初期化(代入)はされないため、宣言前に参照するとundefinedになる。
  • letconstは宣言が巻き上げられても「一時的死のゾーン(Temporal Dead Zone)」があり、宣言前にアクセスするとエラーになる。
console.log(hoistedVar); // undefined(エラーにならない)
var hoistedVar = '値';

// let/constはホイスティングされるがTDZ(Temporal Dead Zone)にある
// console.log(notHoisted); // エラー
let notHoisted = '値';

関数のホイスティング

hoistedFunc(); // '動作する'

function hoistedFunc() {
  console.log('動作する');
}

// 関数式はホイスティングされない
// notHoistedFunc(); // エラー
const notHoistedFunc = function() {};

モジュールパターン

モジュールパターンとは、JavaScriptでコードを整理しつつ、プライベートな変数や関数を隠しつつ公開したい部分だけ外部に見せる仕組みのことです。

主に即時実行関数(IIFE)とオブジェクトを組み合わせて使い、グローバル変数の増加を防ぎつつ、データの隠蔽(カプセル化)ができます。

const MyModule = (function() {
  // プライベート変数
  let privateVar = '外部からアクセス不可';

  // プライベート関数
  function privateMethod() {
    return privateVar;
  }

  // 公開するインターフェース
  return {
    publicMethod: function() {
      return privateMethod();
    }
  };
})();

console.log(MyModule.publicMethod()); // '外部からアクセス不可'
// console.log(MyModule.privateVar); // undefined

解説ポイント

即時実行関数(IIFE)

(function() { ... })(); 

この部分で関数を定義してすぐ実行し、モジュール用の独立したスコープを作っています。

プライベート変数 privateVar

let privateVar = '外部からアクセス不可'; 

関数内部だけで有効な変数で、モジュールの外から直接アクセスできません。

プライベート関数 privateMethod

function privateMethod() { return privateVar; } 

こちらも外部からは見えず、privateVar にアクセスして値を返す役割。

公開するインターフェース(オブジェクトの返却)

return { publicMethod: function() { return privateMethod(); } }; 

publicMethod は外部から呼べる関数で、内部のプライベート関数を使って値を取得し返します。

利用方法と結果

console.log(MyModule.publicMethod()); // '外部からアクセス不可'
// console.log(MyModule.privateVar); // undefined

publicMethodを通じてプライベート変数の値を取得可能。直接 privateVar にアクセスすると undefined で見えません。

まとめ

JavaScriptのスコープ(グローバル・ローカル)を学ぶ理由は、変数や関数の有効範囲を正しく理解し、意図しない上書きやバグを防ぐためです。スコープを把握することで、安全で管理しやすいコードを書けるようになり、大規模なプログラムでも混乱を避けられます。

演習問題

初級問題(3問)

  1. 次のコードの出力結果は?
   let x = 10;

   function test() {
     let x = 20;
     console.log(x);
   }

   test();
   console.log(x);
  1. 次のコードの間違いを指摘し、修正しなさい。
   function printNumbers() {
     for (var i = 0; i < 3; i++) {
       console.log(i);
     }
     console.log('最後:', i);
   }
   printNumbers();
  1. 次のグローバル変数をIIFEを使って保護しなさい。
   let counter = 0;

   function increment() {
     counter++;
     console.log(counter);
   }

中級問題(6問)

  1. 次のコードの出力結果とその理由を説明しなさい。
   var a = 1;
   let b = 2;

   {
     var a = 3;
     let b = 4;
     console.log(a, b);
   }

   console.log(a, b);
  1. 次のクロージャ関数を作成しなさい。

createMultiplier(n)は、引数をn倍する関数を返す。

   const double = createMultiplier(2);
   console.log(double(5)); // 10
  1. 次のコードのホイスティング後の状態を書きなさい。
   console.log(value);
   var value = 10;
   function test() {
     console.log('テスト');
   }
  1. 次のコードの出力結果を順に答えなさい。
   let x = 1;

   function outer() {
     let x = 2;

     function inner() {
       console.log(x);
       let x = 3;
     }

     inner();
   }

   outer();
  1. 次のコードをブロックスコープを使用してリファクタリングしなさい。
   function processData(data) {
     if (data) {
       var processed = data.toUpperCase();
       console.log(processed);
     }
     console.log(processed); // アクセス可能だが非推奨
   }
  1. 次のモジュールパターンを完成させなさい。
   const Calculator = (function() {
     // ここにプライベート変数/関数を定義

     return {
       // ここに公開メソッドを定義
       add: function(a, b) { /* ... */ }
     };
   })();

上級問題(3問)

  1. 次のコードの出力結果とその理由を詳細に説明しなさい。
for (var i = 0; i < 3; i++) { 
    setTimeout(function() { 
        console.log(i);
    }, 100); 
}
for (let j = 0; j < 3; j++) { 
    setTimeout(function() { 
        console.log(j);
    }, 100);
}
  1. 次のような動作をするcreatePrivateStack()関数を実装しなさい。
const stack = createPrivateStack(); 
stack.push(10); 
stack.push(20); 
console.log(stack.pop()); // 20 
console.log(stack.items); // undefined(直接アクセス不可)
  1. スコープチェーンを利用して、次のような動作をするcreateScopeChain()関数を実装しなさい。
const scope = createScopeChain(); 
scope.set('x', 10); 
scope.set('y', 20); 
const childScope = scope.createChild(); 
childScope.set('x', 30); 
console.log(childScope.get('x')); // 30 
console.log(childScope.get('y')); // 20(親スコープから取得) 
console.log(childScope.get('z')); // undefined

解答例

初級問題解答

  1. 出力結果
   20
   10
    let x = 10;

    function test() {
        let x = 20;
        console.log(x); // 20が出力される
    }

    test();
    console.log(x); // 10が出力される
  1. 間違いと修正
   // varをletに変更
   function printNumbers() {
     for (let i = 0; i < 3; i++) {
       console.log(i);
     }
     // console.log('最後:', i); // エラー(iはブロックスコープ内のみ)
   }
  1. IIFEを使用した修正
   const counterModule = (function() {
     let counter = 0;

     return {
       increment: function() {
         counter++;
         console.log(counter);
       }
     };
   })();

   counterModule.increment();

中級問題解答

  1. 出力結果と理由
3 4
3 2
    var a = 1;
    let b = 2;

    {
        var a = 3;
        let b = 4;
        console.log(a, b);  // 3 4 ブロック内の値(letはブロックスコープ)
    }

    console.log(a, b); // 3 2 グローバルスコープの値(varはブロックスコープを無視して上書き)
  1. createMultiplier関数
   function createMultiplier(n) {
     return function(x) {
       return x * n;
     };
   }
  1. ホイスティング後の状態

ホイスティング順番

   var value;
   function test() {
     console.log('テスト');
   }

   console.log(value); // undefined
   value = 10;

コードに順番の付け加え


    // ①var value; 暗黙的value変数宣言
    console.log(value);  // ③ console.log()の実行
    var value = 10;      // ④ console.log()の実行
    function test() {    // ② function test()宣言とロード
        console.log('テスト');
    }
  1. 出力結果

ReferenceError: Cannot access 'x' before initialization
// letのTDZ(一時的デッドゾーン)に引っかかる

    let x = 1;

    function outer() {
        let x = 2;

        function inner() {
            console.log(x); // ブロックスコープの変数を参照するためエラーとなる
            let x = 3;
        }

        inner();
    }

    outer();
  1. ブロックスコープを使用したリファクタリング
   function processData(data) {
     if (data) {
       let processed = data.toUpperCase();
       console.log(processed);
     }
     // console.log(processed); // エラー(アクセス不可)
   }
  1. モジュールパターンの完成例
   const Calculator = (function() {
     let memory = 0;

     function add(a, b) {
       return a + b;
     }

     return {
       add: add,
       addToMemory: function(x) {
         memory += x;
         return memory;
       }
     };
   })();

上級問題解答

  1. 出力結果と理由

3 3 3 // varはループ終了後の値(関数スコープ)
0 1 2 // letはループごとに新しい変数(ブロックスコープ)

for (var i = 0; i < 3; i++) { 
    setTimeout(function() { 
        console.log(i); // 3回「3」が出力される
    }, 100); 
}
for (let j = 0; j < 3; j++) { 
    setTimeout(function() { 
        console.log(j); // 0, 1, 2 が順に出力される
    }, 100);
}
  1. createPrivateStack関数
function createPrivateStack() { 
    const items = []; 
    return { 
        push: function(item) { 
            items.push(item); 
        }, 
        pop: function() { 
            return items.pop();
        }
    };
}
  1. createScopeChain関数
function createScopeChain(parent) { 
    const vars = new Map(); 
    return { 
        set: function(key, value) { 
            vars.set(key, value); 
        }, 
        get: function(key) { 
            if (vars.has(key)) return vars.get(key); 
            if (parent) return parent.get(key); 
            return undefined; 
        }, 
        createChild: function() { 
            return createScopeChain(this); 
        }
    }; 
}