JavaScript | 基礎構文:スコープ・実行コンテキスト - レキシカルスコープ

JavaScript JavaScript
スポンサーリンク

レキシカルスコープって何者?

レキシカルスコープは、
「変数がどこから見えるかは、“コードが書かれている場所”で決まる」
というルールのことです。

もう少し砕くと、

「どの変数が使えるか」は
「どこでその関数を“定義したか”で決まり、
“どこから呼び出したか”では変わらない。

これがレキシカル(静的)スコープです。
JavaScript は、このレキシカルスコープを採用しています。


まずは「どこで定義したか」が効いてくる例

呼び出し場所が違っても、見える変数は同じ

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

const value = "グローバル";

function printValue() {
  console.log(value);
}

function run() {
  const value = "runの中";
  printValue();
}

run(); // 何が出る?
JavaScript

直感的に「run の中から呼んでいるから runの中 が出そう」と思うかもしれませんが、
実際の出力はこうです。

グローバル

なぜか?

printValue は「どこで定義されたか」を見ると、
グローバルスコープにあります。
そのときに見えている value は「グローバルの value」です。

run の中で printValue() を呼んでも、
printValue が「見ているスコープ」は変わりません。

ここが重要です。
レキシカルスコープでは、「関数がどこで“書かれたか”がすべて。
“どこから呼ばれたか”は関係ない。


「呼び出し元ではなく、定義場所」がスコープを決める

もう少し分かりやすく分解してみる

さっきの例を、少し書き換えてみます。

const value = "外";

function outer() {
  const value = "outerの中";

  function inner() {
    console.log(value);
  }

  return inner;
}

const fn = outer();
fn(); // 何が出る?
JavaScript

出力はこうです。

outerの中

ここで起きていることを言葉で追うと、

  1. グローバルに value = "外" がある
  2. outer の中で value = "outerの中" を定義
  3. innerouter の中で定義されている
  4. outer() を呼ぶと、inner が返ってくる
  5. fn() として inner を呼び出す

このとき、inner が参照する value
「自分が定義された場所(outer の中)で見えていた value」です。

fn() をグローバルから呼んでいるからといって、
グローバルの value を見るわけではありません。

ここがレキシカルスコープの核心です。
「関数は、自分が“生まれた場所のスコープ”を覚えている」
と言ってもいいです。


レキシカルスコープと「スコープの入れ子」

コードの“見た目の入れ子”が、そのままスコープの入れ子になる

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

const a = "A";

function f1() {
  const b = "B";

  function f2() {
    const c = "C";
    console.log(a, b, c);
  }

  f2();
}

f1();
JavaScript

出力はこうです。

A B C

ここでのスコープの関係は、

グローバル:a
f1 の中:a, b
f2 の中:a, b, c

という「入れ子」になっています。

ポイントは、
この入れ子構造は「コードの見た目の入れ子」と一致している
ということです。

f2f1 の中に「書かれている」ので、
f1 のスコープを“外側”として持っています。

これが「レキシカル(書かれた場所ベース)のスコープ」です。


「動的スコープ」との違いをざっくりイメージする

JavaScript は「呼び出し元」ではなく「定義場所」を見る

レキシカルスコープの反対の考え方として、
「動的スコープ」というものがあります(JavaScript では採用していません)。

動的スコープの世界では、
「どこから呼ばれたか」でスコープが決まります。

さっきの例を動的スコープ風に考えると、
printValuerun から呼んだときに
run の中の value が見える、という挙動になります。

でも JavaScript はそうではない。
「どこから呼ばれたか」ではなく「どこで書かれたか」で決まる

この違いを意識しておくと、
「なんでこの変数が見えるんだっけ?」というときに迷いにくくなります。


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

「外側の変数を覚えておく関数」が自然に生まれる

レキシカルスコープを理解すると、
クロージャの仕組みもスッと入ってきます。

さっきの createCounter をもう一度。

function createCounter() {
  let count = 0;

  function increment() {
    count += 1;
    console.log(count);
  }

  return increment;
}

const counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3
JavaScript

incrementcreateCounter の中で定義されています。
だから、レキシカルスコープのルールに従って、
「外側のスコープ(createCounter の中)の count」を参照できます。

createCounter() の呼び出しが終わったあとも、
increment は「自分が生まれた場所のスコープ」を覚えているので、
count にアクセスし続けられます。

ここが重要です。
クロージャは「レキシカルスコープがある世界で、
“外側の変数を覚えたまま生き続ける関数”」にすぎない。
土台にあるのは常にレキシカルスコープです。


実務でレキシカルスコープを意識する場面

「この関数から見える変数は何か?」を“定義場所”で考える

例えば、イベントハンドラの中で外側の変数を使うとき。

function setup() {
  const userName = "太郎";

  button.addEventListener("click", () => {
    console.log("こんにちは、" + userName + "さん");
  });
}
JavaScript

この () => { ... }setup の中で定義されています。
だから、userName を参照できます。

setup が呼ばれたあと、
ボタンがクリックされるのはもっと後かもしれません。
それでも userName が見えるのは、
「定義場所のスコープを覚えている」からです。

ここで考えるべきなのは、
「この関数はどこで定義されているか?」
「そのとき、どの変数が見えていたか?」
という視点です。

呼び出しタイミングや呼び出し元ではなく、
「書かれている位置」を基準に考える
これがレキシカルスコープを使いこなすコツです。


初心者として「レキシカルスコープ」で本当に押さえてほしいこと

レキシカルスコープは、
「スコープは“書かれた場所”で決まる」 というルール。

関数がどこから呼ばれるかではなく、
どこで定義されているかで、
見える変数が決まる。

内側の関数は、
自分が書かれている場所の外側スコープを“覚えている”。

このルールのおかげで、

コードの見た目の入れ子構造と、
スコープの入れ子構造が一致する
(=人間にとって理解しやすい)

クロージャのような「外側の変数を覚えた関数」が自然に作れる

という世界が成り立っています。

コードを読むとき・書くときに、
「この関数は“どこで書かれているか”?
そのとき“何が見えていたか”?」

を意識してみてください。

それができるようになると、
スコープの話は「難しい理論」ではなく、
「当たり前の空気感」として体に馴染んでいきます。

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