「変数解決の仕組み」って何を指しているか
ここでいう「変数解決の仕組み(順序)」は、
「ある名前(変数名)を書いたときに、JavaScript が“どの値を使うか”をどう決めているか
という話です。
同じ名前の変数が、
グローバル・関数の中・ブロックの中など、
いろんな場所に存在しうる世界で、
「この value はどれのこと?」
を決めるルールが必要になります。
そのルールが「変数解決の順序」です。
大前提:スコープは“内側から外側へ”たどる
探索の基本イメージ
JavaScript が変数を探すときの基本は、
「今いるスコープから外側へ向かって順番に探していく」 です。
「今いる場所」
→ そこになければ「一つ外側」
→ それでもなければ「さらに外側」
→ 最後までなければエラー
という流れです。
この「今いる場所から外へ外へ」というイメージを
まず頭に置いてください。
具体例で追う:関数とネストしたスコープ
内側・外側に同じ名前がある場合
次のコードを見てください。
const value = "グローバル";
function outer() {
const value = "outer";
function inner() {
const value = "inner";
console.log(value);
}
inner();
}
outer();
JavaScriptconsole.log(value); がどの value を指すかを、
JavaScript はこういう順序で決めます。
innerのスコープの中にvalueはあるか?
→ ある("inner")ので、これを使う- そこで探索終了。外側は見に行かない
結果として、出力は "inner" になります。
もし inner の中に value がなかったら、
次のように外側へ進みます。
const value = "グローバル";
function outer() {
const value = "outer";
function inner() {
console.log(value);
}
inner();
}
outer();
JavaScriptこの場合の探索はこうです。
innerのスコープにvalueはあるか?
→ ない- 一つ外側の
outerのスコープにvalueはあるか?
→ ある("outer")ので、これを使う - そこで探索終了
結果は "outer" になります。
さらに outer にもなければ、
グローバルまでたどっていきます。
ここが重要です。
「一番内側のスコープから外側へ向かって、同じ名前を探していく。
見つかった時点でそこで止まる。」
これが変数解決の基本ルールです。
ブロックスコープも同じルールで解決される
if や for の中でも「内側優先」
const message = "グローバル";
function show() {
const message = "関数内";
if (true) {
const message = "ブロック内";
console.log(message);
}
}
show();
JavaScriptconsole.log(message); の変数解決はこうです。
- if ブロックのスコープに
messageはあるか?
→ ある("ブロック内")ので、これを使う - そこで探索終了
もしブロック内に message がなければ、
関数スコープの message を見に行きます。
const message = "グローバル";
function show() {
const message = "関数内";
if (true) {
console.log(message);
}
}
show();
JavaScriptこの場合は、
- ブロック内に
messageはない - 一つ外側の関数スコープに
messageがある("関数内") - それを使う
という流れになります。
ここでも、
「一番近いスコープの同名変数が優先される」
というルールが貫かれています。
変数が見つからなかった場合どうなるか
最後まで行ってもなければエラー
次のコードを見てください。
function test() {
console.log(value);
}
test();
JavaScriptvalue はどこにも宣言されていません。
変数解決の流れはこうです。
testのスコープにvalueはあるか?
→ ない- 一つ外側(グローバル)に
valueはあるか?
→ ない - もう外側はないので、
ReferenceError
結果として、
ReferenceError: value is not defined
になります。
ここが重要です。
「見つからなかったら undefined ではなく、ReferenceError」。
“宣言されていない名前”は、そもそも使えない。
TDZ と変数解決の関係(軽くおさらい)
「見つかったけど、まだ使えない」状態もある
let / const の場合、
「スコープの中には存在しているけど、まだ TDZ 中で使えない」
という状態があります。
{
console.log(num); // ここ
let num = 10;
}
JavaScript変数解決の流れとしては、
- このブロックのスコープに
numはあるか?
→ ある(let numが宣言されている) - しかし、まだ宣言行に到達していないので TDZ 中
- そのため
ReferenceError: Cannot access 'num' before initialization
つまり、
「変数解決としては“見つかっている”けれど、
“状態としてはまだ使えない”」
というパターンもある、ということです。
ここが重要です。
変数解決の順序と、TDZ による「使える/使えない」の判定は別のレイヤー。
まず“どの変数か”を決めてから、“今それを使っていいか”がチェックされる。
this は「変数解決」とは別枠
this はスコープチェーンでは探さない
this はよく混同されますが、
変数解決のスコープチェーンとは別の仕組みで決まります。
const obj = {
value: 1,
show() {
console.log(this.value);
},
};
obj.show();
JavaScriptここで this が obj になるのは、
「obj.show() という呼び出し方をしたから」であって、
スコープチェーンをたどって this という変数を探しているわけではありません。
変数解決の話は、value, message, count などの「名前付きの識別子」を
スコープチェーンで探す仕組みの話です。
this は「呼び出し方」で決まる別枠の存在だ、
と分けて考えると頭がスッキリします。
初心者として「変数解決の順序」で本当に押さえてほしいこと
変数解決の仕組みは、
一言でまとめるとこうです。
「今いるスコープから外側へ向かって、
一番近い同名の変数を探し、見つかった時点でそれを使う」
このとき、
内側に同じ名前があれば、それが外側を“隠す”(シャドーイング)
最後まで見つからなければ ReferenceErrorlet / const の場合、見つかっても TDZ 中ならエラー
というルールが乗っかっています。
コードを書くとき・読むときに、
一度立ち止まって、
「この行で、この名前は“どのスコープの変数”を指している?」
「内側に同じ名前はないか?」
と自分に問いかけてみてください。
その問いを繰り返すうちに、
スコープと変数解決は“暗記するもの”ではなく、
「自然に見える構造」 として体に馴染んでいきます。

