Zenn
🔨

JavaScript レキシカルスコープとは?

2025/03/10に公開

クロージャの一種

https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Closures

公式の解説によると

クロージャ
クロージャは、組み合わされた(囲まれた)関数と、その周囲の状態(レキシカル環境)への参照の組み合わせです。言い換えれば、クロージャは関数にその外側のスコープにアクセスする機能を提供します。JavaScript では、クロージャは関数が作成されるたびに、関数作成時点で作成されます。

レキシカルスコープ
次のような関数を考えてみてください。

function init() {
  var name = "Mozilla"; // name は、init が作成するローカル変数
  function displayName() {
    // displayName() は内部に閉じた関数
    console.log(name); // 親関数で宣言された変数を使用
  }
  displayName();
}
init();

init() 関数はローカル変数 name を作成し、それから関数 displayName() を定義しています。displayName() は init() の中で定義されている内部関数で、その関数本体の内部でしか利用できません。displayName() 自体はローカル変数を持っていませんが、外側のスコープで宣言された変数にアクセスできるので、displayName() では親関数 init() で宣言された変数 name を使用できます。しかし、 displayName() に同じローカル変数があればそれが使われます。

この JSFiddle リンクを使用してコードを実行すると、displayName() 関数内の console.log() 文が、その親関数で宣言されている name 変数の値を正常に表示していることに注意してください。これはレキシカルスコープの例で、関数が入れ子になっているときにパーサーがどのように変数名を解決するかを記述したものです。レキシカルという言葉は、レキシカルスコープがソースコード内で変数が宣言された場所を使用して、その変数が利用できる場所を決定するという事実を示しています。入れ子の関数は、その外側のスコープで宣言された変数にアクセスすることができます。

MDNの解説わかりにくい

難しい言葉のように超える人もいるかも

違う表現だと...

例を見てみましょう:

function init() {
  var name = "Mozilla"; // name はinit関数内のローカル変数
  
  function displayName() { // displayNameはinit内の内部関数
    console.log(name); // 親関数のnameを使用できる
  }
  
  displayName();
}

init(); // "Mozilla"を表示

この例では:

  1. init()関数の中にnameというローカル変数があります
  2. init()の中にdisplayName()という内部関数を定義しています
  3. displayName()関数は自分の中にname変数を持っていませんが、親関数であるinit()name変数にアクセスできます

これがレキシカルスコープの特徴です:関数は自分が定義された環境の変数にアクセスできます。つまり、内側の関数は外側の関数の変数を「見る」ことができます。

もしdisplayName()内部にもname変数があれば、その内部の変数が優先されます:

function init() {
  var name = "Mozilla";
  
  function displayName() {
    var name = "Firefox"; // 自分の変数を優先
    console.log(name);
  }
  
  displayName();
}

init(); // "Firefox"を表示

レキシカルスコープは、コードを書いた「場所」によって変数のアクセス範囲が決まるため「静的スコープ」とも呼ばれます。これは実行時の状況ではなく、コードの構造によって決まるのが特徴です。

公式以外の使用例知りたく作ってみた

カウンターですね。

function createCounter() {
    let count = 0;  // 外部関数のローカル変数
    
    return () => {  // 内部関数がクロージャを形成
      count++;  // 外部関数のcount変数にアクセス
      return count;
    };
  }
  
const counter = createCounter();
console.log(counter());  // 1
console.log(counter());  // 2
console.log(counter());  // 3

ボタンをクリックするイベントが発生したら実行される関数

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <button id="submitButton">送信</button>
    <button id="cancelButton">キャンセル</button>
</body>
<script>
    function setupButton(buttonId, message) {
        const button = document.getElementById(buttonId);

        button.addEventListener('click', (() => {
            // 外部スコープの変数messageにアクセス
            console.log(message);
        }));
    }

    setupButton('submitButton', 'フォームが送信されました');
    setupButton('cancelButton', '操作がキャンセルされました');
</script>

</html>

まとめ

レキシカルスコープ:コード内での変数の参照範囲を決める仕組みで、変数や関数が宣言された「場所」によって決まります。関数が他の関数の中で定義されているとき、内側の関数は外側の関数の変数にアクセスできるという性質です。

Discussion

ログインするとコメントできます