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

JavaScript JavaScript
スポンサーリンク

レキシカルスコープとは何か

レキシカルスコープ(静的スコープ)とは、「変数がどこから見えるかが、コードを“書いた場所”で決まる」という仕組みのことです。ポイントは「関数をどこで呼ぶか」ではなく「関数をどこで定義したか」が効くこと。関数の中から外側の変数へアクセスするとき、JavaScript はソースコード上の入れ子関係(スコープチェーン)に沿って内側から外側へ順番に探します。


実行コンテキストとの関係

プログラムが動くとき、JavaScript は「実行コンテキスト」という実行の枠組みを作ります。関数を呼ぶたびに関数実行コンテキストが作られ、その関数が「どこに定義されていたか」に基づくスコープチェーンが紐づきます。レキシカルスコープはコンテキスト生成時に「定義位置の外側スコープ」を覚えておくので、実行時に「呼び出し元の場所」が変わっても、見える変数は変わりません。


例題で理解する(定義位置で決まることを体感)

例題1:外側の変数を参照する

const message = "外側のメッセージ";

function show() {
  console.log(message); // 外側にある message を参照
}

show(); // 外側のメッセージ
JavaScript

show は「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
JavaScript

inner は「outer の中で定義された」ため、outera が見えます。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
JavaScript

createCounter の実行が終わっても count は消えません。incrementcount への参照を保持するからです。これがクロージャで、レキシカルスコープの直接的な応用です。


よくある誤解と落とし穴(深掘り)

レキシカルスコープでは「呼び出した場所の変数」は見えません。見えるのは「定義された場所の外側スコープだけ」です。

例題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();
JavaScript

loggermakeLogger の定義位置に結びついているため、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 を使い、シャドーイングや呼び出し元の変数が見えない点を理解しておくと、予測しやすく安全なコードが書けます。

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