レキシカルスコープとは何か
レキシカルスコープ(静的スコープ)とは、「変数がどこから見えるかが、コードを“書いた場所”で決まる」という仕組みのことです。ポイントは「関数をどこで呼ぶか」ではなく「関数をどこで定義したか」が効くこと。関数の中から外側の変数へアクセスするとき、JavaScript はソースコード上の入れ子関係(スコープチェーン)に沿って内側から外側へ順番に探します。
実行コンテキストとの関係
プログラムが動くとき、JavaScript は「実行コンテキスト」という実行の枠組みを作ります。関数を呼ぶたびに関数実行コンテキストが作られ、その関数が「どこに定義されていたか」に基づくスコープチェーンが紐づきます。レキシカルスコープはコンテキスト生成時に「定義位置の外側スコープ」を覚えておくので、実行時に「呼び出し元の場所」が変わっても、見える変数は変わりません。
例題で理解する(定義位置で決まることを体感)
例題1:外側の変数を参照する
const message = "外側のメッセージ";
function show() {
console.log(message); // 外側にある message を参照
}
show(); // 外側のメッセージ
JavaScriptshow は「message が見える場所」で定義されています。呼び出し場所に関係なく、定義時の外側スコープから message を見つけます。
例題2:呼び出し場所は影響しない(レキシカルの重要性を深掘り)
const a = "グローバルのA";
function outer() {
const a = "outerのA";
function inner() {
console.log(a);
}
return inner;
}
const fn = outer(); // inner を取得(ここで outer のスコープが紐づく)
const a2 = "別の場所のA";
fn(); // outerのA
JavaScriptinner は「outer の中で定義された」ため、outer の a が見えます。fn をグローバルで呼んでも、呼び出し位置の a2 は関係ありません。これがレキシカルスコープの本質です。
例題3:変数のシャドーイング(内側が優先)
const name = "太郎";
function greet() {
const name = "花子"; // 外側と同名だが、内側が優先される
console.log(`こんにちは、${name}`);
}
greet(); // こんにちは、花子
console.log(name); // 太郎
JavaScript同じ名前の変数が内外にあると、内側が外側を「隠す(シャドーイング)」ため、関数内では内側の値が使われます。
レキシカルスコープとクロージャ(重要ポイントを深掘り)
クロージャとは、「関数が、定義された場所の外側スコープ(変数)を”覚えて”持ち歩く仕組み」です。レキシカルスコープにより、関数が返された後でも、外側の変数へアクセスできます。
例題4:カウンタ関数(外側変数を保持)
function createCounter() {
let count = 0; // この変数を inner が覚える
function increment() {
count++;
return count;
}
return increment;
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
JavaScriptcreateCounter の実行が終わっても count は消えません。increment が count への参照を保持するからです。これがクロージャで、レキシカルスコープの直接的な応用です。
よくある誤解と落とし穴(深掘り)
レキシカルスコープでは「呼び出した場所の変数」は見えません。見えるのは「定義された場所の外側スコープだけ」です。
例題5:呼び出し元の変数は見えない
const x = "グローバル";
function makeLogger() {
return function log() {
console.log(x);
};
}
function run() {
const x = "run内のx";
const logger = makeLogger(); // logger は makeLogger の場所に紐づく
logger(); // グローバルのx
}
run();
JavaScriptlogger は makeLogger の定義位置に結びついているため、run 内の x は参照しません。「どこで呼ぶか」ではなく「どこで定義したか」で決まります。
var と let/const の違いが及ぼす影響(押さえておきたい重要点)
var は関数スコープ、let/const はブロックスコープです。レキシカルスコープの探索は「内側から外側へ」ですが、どの範囲が「内側」になるかは宣言方法で変わります。
for (var i = 0; i < 3; i++) {
// var はブロックスコープを作らない
}
console.log(i); // 3(外から見える)
for (let j = 0; j < 3; j++) {
// let はブロックスコープ内だけ
}
console.log(j); // エラー(外から見えない)
JavaScript「どこまでがスコープか」を正しく作ることが、レキシカルスコープの振る舞いを期待通りにする鍵です。基本は let/const を使い、var の広い可視範囲によるバグを避けましょう。
まとめ
レキシカルスコープは「定義位置で見える変数が決まる」仕組みで、スコープチェーンに沿って内側から外側へ探索します。呼び出し位置は影響せず、クロージャによって「外側の変数を覚えて持ち歩く」ことが可能です。スコープの形を正しく作るために let/const を使い、シャドーイングや呼び出し元の変数が見えない点を理解しておくと、予測しやすく安全なコードが書けます。
