JavaScript | 基礎構文:スコープ・実行コンテキスト - 変数解決の仕組み(順序)

JavaScript JavaScript
スポンサーリンク

「変数解決の仕組み」って何を指しているか

ここでいう「変数解決の仕組み(順序)」は、
「ある名前(変数名)を書いたときに、JavaScript が“どの値を使うか”をどう決めているか
という話です。

同じ名前の変数が、
グローバル・関数の中・ブロックの中など、
いろんな場所に存在しうる世界で、

「この value はどれのこと?」
を決めるルールが必要になります。

そのルールが「変数解決の順序」です。


大前提:スコープは“内側から外側へ”たどる

探索の基本イメージ

JavaScript が変数を探すときの基本は、
「今いるスコープから外側へ向かって順番に探していく」 です。

「今いる場所」
→ そこになければ「一つ外側」
→ それでもなければ「さらに外側」
→ 最後までなければエラー

という流れです。

この「今いる場所から外へ外へ」というイメージを
まず頭に置いてください。


具体例で追う:関数とネストしたスコープ

内側・外側に同じ名前がある場合

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

const value = "グローバル";

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

  function inner() {
    const value = "inner";
    console.log(value);
  }

  inner();
}

outer();
JavaScript

console.log(value); がどの value を指すかを、
JavaScript はこういう順序で決めます。

  1. inner のスコープの中に value はあるか?
    → ある("inner")ので、これを使う
  2. そこで探索終了。外側は見に行かない

結果として、出力は "inner" になります。

もし inner の中に value がなかったら、
次のように外側へ進みます。

const value = "グローバル";

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

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

  inner();
}

outer();
JavaScript

この場合の探索はこうです。

  1. inner のスコープに value はあるか?
    → ない
  2. 一つ外側の outer のスコープに value はあるか?
    → ある("outer")ので、これを使う
  3. そこで探索終了

結果は "outer" になります。

さらに outer にもなければ、
グローバルまでたどっていきます。

ここが重要です。
「一番内側のスコープから外側へ向かって、同じ名前を探していく。
見つかった時点でそこで止まる。」
これが変数解決の基本ルールです。


ブロックスコープも同じルールで解決される

if や for の中でも「内側優先」

const message = "グローバル";

function show() {
  const message = "関数内";

  if (true) {
    const message = "ブロック内";
    console.log(message);
  }
}

show();
JavaScript

console.log(message); の変数解決はこうです。

  1. if ブロックのスコープに message はあるか?
    → ある("ブロック内")ので、これを使う
  2. そこで探索終了

もしブロック内に message がなければ、
関数スコープの message を見に行きます。

const message = "グローバル";

function show() {
  const message = "関数内";

  if (true) {
    console.log(message);
  }
}

show();
JavaScript

この場合は、

  1. ブロック内に message はない
  2. 一つ外側の関数スコープに message がある("関数内"
  3. それを使う

という流れになります。

ここでも、
「一番近いスコープの同名変数が優先される」
というルールが貫かれています。


変数が見つからなかった場合どうなるか

最後まで行ってもなければエラー

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

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

test();
JavaScript

value はどこにも宣言されていません。

変数解決の流れはこうです。

  1. test のスコープに value はあるか?
    → ない
  2. 一つ外側(グローバル)に value はあるか?
    → ない
  3. もう外側はないので、ReferenceError

結果として、

ReferenceError: value is not defined

になります。

ここが重要です。
「見つからなかったら undefined ではなく、ReferenceError」。
“宣言されていない名前”は、そもそも使えない。


TDZ と変数解決の関係(軽くおさらい)

「見つかったけど、まだ使えない」状態もある

let / const の場合、
「スコープの中には存在しているけど、まだ TDZ 中で使えない」
という状態があります。

{
  console.log(num); // ここ
  let num = 10;
}
JavaScript

変数解決の流れとしては、

  1. このブロックのスコープに num はあるか?
    → ある(let num が宣言されている)
  2. しかし、まだ宣言行に到達していないので TDZ 中
  3. そのため ReferenceError: Cannot access 'num' before initialization

つまり、

「変数解決としては“見つかっている”けれど、
“状態としてはまだ使えない”」

というパターンもある、ということです。

ここが重要です。
変数解決の順序と、TDZ による「使える/使えない」の判定は別のレイヤー。
まず“どの変数か”を決めてから、“今それを使っていいか”がチェックされる。


this は「変数解決」とは別枠

this はスコープチェーンでは探さない

this はよく混同されますが、
変数解決のスコープチェーンとは別の仕組みで決まります。

const obj = {
  value: 1,
  show() {
    console.log(this.value);
  },
};

obj.show();
JavaScript

ここで thisobj になるのは、
obj.show() という呼び出し方をしたから」であって、
スコープチェーンをたどって this という変数を探しているわけではありません。

変数解決の話は、
value, message, count などの「名前付きの識別子」を
スコープチェーンで探す仕組みの話です。

this は「呼び出し方」で決まる別枠の存在だ、
と分けて考えると頭がスッキリします。


初心者として「変数解決の順序」で本当に押さえてほしいこと

変数解決の仕組みは、
一言でまとめるとこうです。

「今いるスコープから外側へ向かって、
一番近い同名の変数を探し、見つかった時点でそれを使う」

このとき、

内側に同じ名前があれば、それが外側を“隠す”(シャドーイング)
最後まで見つからなければ ReferenceError
let / const の場合、見つかっても TDZ 中ならエラー

というルールが乗っかっています。

コードを書くとき・読むときに、
一度立ち止まって、

「この行で、この名前は“どのスコープの変数”を指している?」
「内側に同じ名前はないか?」

と自分に問いかけてみてください。

その問いを繰り返すうちに、
スコープと変数解決は“暗記するもの”ではなく、
「自然に見える構造」 として体に馴染んでいきます。

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