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

JavaScript JavaScript
スポンサーリンク

変数解決とは何か(どの値を使うかを決める仕組み)

「変数解決」とは、コードの中で名前(例:count, user)を使ったときに、JavaScript が「その名前が指す具体的な値」をどのスコープから見つけるかを決める手順のことです。重要なのは、JavaScriptは「定義された位置」に基づくレキシカルスコープを使い、内側から外側へ段階的に探します。呼び出し位置ではなく、定義位置で探索の起点が決まる点が本質です。


探索の順序(スコープチェーンの流れ)

変数名を解決するときの基本の順番は「一番近いところから、外へ外へ」です。直感は「今いる部屋から廊下、さらに外の建物へ」という階層移動。

現在のブロック(TDZに注意)

let/const で宣言された同名がそのブロック {} 内にあれば最優先で使われます。ただし、初期化前(TDZ)に触ると ReferenceError になります。近いものが優先されるが、準備が整っていないと使えない、というルールです。

関数のローカルスコープ

ブロックで見つからなければ、関数内の他のブロックや関数の引数・ローカル変数を探します。ここで同名が見つかればそれが使われます(シャドーイングの典型)。

外側の関数やモジュールのスコープ

さらに外側の関数で宣言された変数、またはモジュール(ファイル)トップレベルの変数を探します。レキシカルに「定義された位置の外側」へ広がるのがポイントです。

最後にグローバルスコープ

どこにもなければ、グローバルスコープ(ブラウザならほぼ window、ESMではモジュール単位で分離)まで探します。それでもなければ ReferenceError です。


例題で手触りをつかむ(近いほうが勝つ・定義位置が効く)

例題1:内側が外側を隠す(シャドーイング)

const name = "太郎";      // グローバル

function greet() {
  const name = "花子";    // 関数内(近いほう)
  console.log(name);      // 花子
}

greet();
console.log(name);        // 太郎
JavaScript

同名が内側にあれば、外側は隠されます。探索は「内→外」の順です。

例題2:ブロック → 関数 → グローバルの順で探す

const value = "global";

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

  if (true) {
    const value = "block";
    console.log(value);   // block(最も近い)
  }

  console.log(value);     // function(次に近い)
}

demo();
console.log(value);       // global(外の最後)
JavaScript

一番近いブロックから、関数、グローバルの順で決まります。

例題3:呼び出し場所は関係ない(レキシカルスコープ)

const a = "global A";

function outer() {
  const a = "outer A";
  function inner() {
    console.log(a);
  }
  return inner;
}

const fn = outer();
fn(); // outer A
JavaScript

inner は「outerの中で定義された」ため、呼び出し場所がグローバルでも「outerのA」を見ることになります。


TDZ と var の違い(未初期化の扱いを深掘り)

let/const は宣言行までの間は TDZ にあり、参照すると ReferenceErrorvar は関数スコープで、宣言前でも undefined として読めます。

console.log(x); // ReferenceError(TDZ)
let x = 1;

function f() {
  console.log(y); // undefined(varは巻き上げ)
  var y = 2;
}
f();
JavaScript

重要な直感: 「エラーで止まる方(TDZ)のほうが、未初期化のまま計算に混ざる事故を防げる」。基本は let/const を使い、宣言・初期化を使用より前に置く習慣を持つと安全です。


引数と同名の外側変数(優先順位の深掘り)

関数の引数は、その関数の最も近いスコープに属します。外側の同名変数より引数が優先されます。さらに、デフォルト引数の評価は「引数リスト内のスコープ」で行われるため、同名を参照するとTDZに落ちやすい点に注意。

const rate = 0.1;

// 引数が優先される例
function priceWithTax(price, rate) {
  return Math.floor(price * (1 + rate));
}
console.log(priceWithTax(1000, 0.08)); // 1080

// デフォルト引数の落とし穴(避けるべき)
function bad(price, rate = rate) {      // ここで内側のrate(未初期化)を参照
  return price;
}
// bad(1000); // ReferenceError
JavaScript

安全策は「別名の定数を用意して参照する」か「引数名を外側と衝突させない」ことです。


モジュールとグローバルの境界(探索の終点を理解する)

ES Modules では、ファイルのトップは「モジュールスコープ」であり他のファイルと自動共有されません。変数解決の最終地点は「そのモジュールの外側(グローバル)」ですが、通常は export/importで明示的に共有します。無意識にグローバルへ依存せず、必要なものは import して使うと、探索の終点が明確になりバグを防げます。

// config.js
export const TAX_RATE = 0.1;

// calc.js
import { TAX_RATE } from "./config.js";
export function priceWithTax(p) { return Math.floor(p * (1 + TAX_RATE)); }
JavaScript

まとめ(安全な変数解決のための感覚)

変数解決は「内側から外側へ」段階的に探す仕組みで、定義位置(レキシカル)がすべての起点になります。近いスコープが優先され、let/const は初期化前に触るとエラー(TDZ)、引数は最も近いスコープとして外側より優先されます。宣言を使用より前に置く、同名衝突を避ける、モジュールで明示的に共有する——この3つを守るだけで、変数解決は直感的になり、予測可能で安全なコードを書けるようになります。

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