定義
レキシカルスコープとは、変数のスコープがコードを書いた場所(定義した場所) によって決まる仕組みのこと。
関数が「どこで呼ばれたか」ではなく、「どこで定義されたか」によってスコープが決まる。
- JavaScriptはレキシカルスコープ(静的スコープ)を採用している
- 関数は定義時のスコープチェーンを保持する
- これがクロージャの基盤になっている
コード例① 基本
外側の変数に内側からアクセスできる。
const name = 'Kengo';
function greet() {
// name は外側のスコープで定義されているが参照できる
console.log(`Hello, ${name}`);
}
greet(); // Hello, Kengo
コード例② 呼び出し場所は関係ない
定義時のスコープで変数が決まる(呼び出し場所ではない)。
const x = 'outer';
function showX() {
console.log(x); // 定義時に outer スコープの x を参照
}
function callShowX() {
const x = 'inner'; // 別の x を宣言しても影響しない
showX();
}
callShowX(); // 'outer' → inner ではない
コード例③ クロージャとの関係
レキシカルスコープがあるからクロージャが成立する。
function makeCounter() {
let count = 0;
return function() {
count++;
// 返された関数は定義時のスコープ(count)を保持している
console.log(count);
};
}
const counter = makeCounter();
counter(); // 1
counter(); // 2
学習当初につまずいたこと
① 「定義した場所」と「呼び出した場所」の違いがわからなかった
最初は「関数を呼び出した場所の変数が使われる」と思い込んでいた。
下のコードで 'inner' が出力されると予想して、実際には 'outer' が出て混乱した。
const x = 'outer';
function showX() {
console.log(x);
}
function callShowX() {
const x = 'inner';
showX(); // 'outer' が出る → 呼び出し場所の x ではなく、定義時の x
}
callShowX();
最初は「showX() が callShowX の中に書いてあるから、callShowX のスコープが使われる」と読んでいた。
でも showX 自体はグローバルスコープで定義されている。callShowX は showX を呼んでいるだけで、showX の定義場所とは別の話。
グローバルスコープ
├─ const x = 'outer' ← showX が見ているのはここ
├─ function showX() {} ← showX はここで定義されている
└─ function callShowX() {
const x = 'inner'; ← showX には関係ない
showX(); ← 呼んでいるだけ
}
腑に落ちたポイント: 「showX() という呼び出しが callShowX の中にあっても、showX の住所(定義場所)はグローバル」と考えたら整理できた。呼び出し場所ではなく、定義場所のスコープが使われる。
② スコープチェーンのイメージがつかめなかった
「スコープが連鎖している」と言われてもピンとこなかった。
図で表すと理解しやすかった。
グローバルスコープ
└─ 関数A のスコープ
└─ 関数B のスコープ ← ここから外側に向かって変数を探しに行く
変数を参照するとき、JSエンジンは内側から外側へ順番に探す。
見つかった時点で止まる。グローバルまで見つからなければ ReferenceError。
腑に落ちたポイント: 「外側には出られるが、内側には入れない」と覚えた。
③ コードを読んでも変数がどこを参照しているか追えなかった
ネストが深くなると「この変数どこで定義されてる?」がわからなくなっていた。
const level = 'global';
function outer() {
const level = 'outer';
function inner() {
const level = 'inner';
console.log(level); // どの level? → 一番近い内側 = 'inner'
}
inner();
}
腑に落ちたポイント: 「使っている変数の宣言(const / let / var)を、内側から外側に向かって探す」という読み方を習慣にした。エディタの「定義へ移動」機能も活用した。
- スコープはどこで定義したかで決まる(どこで呼んだかではない)
- 内側のスコープから外側の変数には参照できる(逆はできない)
- この仕組みのおかげでクロージャが使える
- JSを読むとき「この変数はどこで定義された?」と考えると理解しやすい