閉じる

「スコープ」と「コンテキスト」って何が違うの?初学者がつまずきやすいポイントをやさしく整理

プログラミングを学び始めると、必ずと言っていいほど出てくるのが「スコープ」「コンテキスト」という言葉です。

どちらも「その変数(やthis)が、どこから・どうやって使えるのか」に関わる重要な概念ですが、名前も雰囲気も似ているため、初学者の方が混乱しがちなポイントでもあります。

本記事では、JavaScriptを例にしながら、スコープとコンテキストの違いをコードと図解で丁寧に整理していきます。

スコープとは何かをコード例で理解する

スコープの基本定義

スコープ(scope)とは、「ある変数や関数が、どこから参照できるかを決める“有効範囲”」のことです。

もう少し具体的に言うと、プログラムの中で変数を宣言したとき、その変数をどの行からどの行まで使えるのかを決める「なわばり」のようなものがスコープです。

JavaScriptでシンプルな例を見てみます。

JavaScript
function foo() {
  const x = 10;
  console.log(x); // 10
}

foo();

console.log(x); // エラー: x is not defined

このコードでは、xfoo関数の中で宣言されています。

そのためxのスコープはfoo関数の内部に限定され、外側から参照しようとするとエラーになります。

ここではまだ「スコープ = 変数の見える範囲」と押さえておけば十分です。

グローバルスコープとローカルスコープの違い

スコープを語るときによく出てくるのが、グローバルスコープローカルスコープです。

  • グローバルスコープ
    プログラム全体から参照できる、一番外側のスコープ
  • ローカルスコープ
    関数やブロックの中など、限られた範囲からだけ参照できるスコープ
JavaScript
// グローバルスコープ
const globalValue = "global";

function show() {
  // ローカルスコープ (show関数の内部)
  const localValue = "local";

  console.log(globalValue); // OK: "global"
  console.log(localValue);  // OK: "local"
}

show();

console.log(globalValue); // OK: "global"
console.log(localValue);  // エラー: localValue is not defined

内側から外側は見えるが、外側から内側は見えないというのが、グローバルスコープとローカルスコープの関係です。

ブロックスコープ(if・forなど)と関数スコープ

JavaScriptでは、宣言の仕方によってスコープのルールが変わります。

特にブロックスコープ関数スコープという違いが重要です。

ブロックスコープ (let / const)

ifforなどのブロックで囲まれた範囲をスコープとするのがブロックスコープです。

letconstで宣言した変数はブロックスコープを持ちます。

JavaScript
if (true) {
  const message = "Hello";
  console.log(message); // "Hello"
}

console.log(message); // エラー: message is not defined

関数スコープ (var)

一方、varで宣言した変数は関数スコープを持ち、ifforブロックは無視されて、関数全体がスコープになります。

JavaScript
function test() {
  if (true) {
    var value = 1;
  }

  console.log(value); // 1 (ブロックの外でも見える)
}

test();

この違いは「なぜvarはおすすめされないのか」を理解する上でも重要です。

変数の有効範囲とライフタイム

スコープは「どこから見えるか」を表しますが、もう一つ重要なのがライフタイム(寿命)です。

ライフタイムとは、変数がメモリ上に存在している期間を指します。

JavaScript
function counter() {
  let count = 0; // ここでcountが生成される
  count++;
  console.log(count);
} // 関数が終わると、countは破棄される

counter(); // 1
counter(); // 1 (毎回新しいcountが生成される)

この例では、counterを呼ぶたびに新しくcountが作られ、関数の終わりで破棄されます。

スコープとライフタイムは密接に関係していて、そのスコープから到達できなくなった変数は、やがてガベージコレクションの対象になります。

コンテキストとは何かを直感的に理解する

コンテキストの基本定義

コンテキスト(context)とは、簡単に言うと「あるコードが“どのオブジェクトの文脈で”実行されているか」です。

JavaScriptでは特にthisが何を指しているか、という形で表面化します。

  • スコープ: 変数がどこから見えるか(定義場所で決まる)
  • コンテキスト: thisが何を指すか(呼び出し方で決まる)

ここが記事全体のキーポイントなので、意識して読み進めてみてください。

thisが指すものと実行コンテキスト

JavaScriptでは、実行中の関数の「持ち主」や「呼び出し元のオブジェクト」を表すキーワードとしてthisが使われます。

このthisが何を指しているかが、その関数のコンテキストです。

JavaScript
const user = {
  name: "Alice",
  sayHello() {
    console.log("Hello, " + this.name);
  }
};

user.sayHello(); // "Hello, Alice"

このときthisuserオブジェクトを指しています。

つまり、この実行のコンテキストはuserです。

コンテキストはよく「実行コンテキスト(execution context)」という言い方もされますが、初学者のうちは「今、その関数は“誰として”動いているか」とイメージすると理解しやすくなります。

関数の呼び出し方で変わるコンテキスト

重要なのは、コンテキストは「関数がどう呼ばれたか」で変わるという点です。

同じ関数でも、呼び出し方が違えばthisも変わります。

JavaScript
const user = {
  name: "Alice",
  sayHello() {
    console.log("Hello, " + this.name);
  }
};

const otherUser = { name: "Bob" };

user.sayHello(); // "Hello, Alice"

const fn = user.sayHello; // メソッドを変数に代入
fn(); // "Hello, undefined" など (環境により異なる)

// callを使ってコンテキストを指定
fn.call(otherUser); // "Hello, Bob"

ここでは3パターンあります。

  1. user.sayHello()
    → this はuser
  2. fn() (単なる関数呼び出し)
    → this はundefined もしくはグローバルオブジェクト(非strictモード時)
  3. fn.call(otherUser)
    → this はotherUser

「どこに書かれているか」ではなく「どう呼ばれたか」でthisが決まる、というのがコンテキストの本質です。

オブジェクトメソッドとコンテキストの関係

オブジェクトの中に定義された関数をメソッドと呼びます。

メソッドは「そのオブジェクトをコンテキストとして動く関数」と捉えると理解しやすくなります。

JavaScript
const calculator = {
  value: 0,
  add(amount) {
    this.value += amount;
  }
};

calculator.add(5);
console.log(calculator.value); // 5

ここではaddのコンテキストは常にcalculatorであり、this.valuecalculator.valueを意味しています。

ところが、メソッドを変数に代入したり、コールバックに渡したりすると、「誰のメソッドとして呼ばれているか」が失われ、コンテキストが変わってしまうことがあります。

これは後半の「罠」のところで詳しく見ていきます。

「スコープ」と「コンテキスト」の違いを整理する

スコープとコンテキストの混同パターン

初学者の方は、よく次のように混同してしまいます。

  • 「thisが見えないのでスコープの問題だ」
  • 「関数の中から外のthisが見えない」

しかし、変数が見えるかどうかと、thisが何を指すかは、まったく別の仕組みで決まっています。

  • 変数: スコープ(定義場所)で決まる
  • this: コンテキスト(呼び出し方)で決まる

「どこで定義されたか」(スコープ)と「どう呼ばれたか」

スコープは「どこで定義されたか」で一生決まります。

途中で変わることはありません。

一方、コンテキストは「どう呼ばれたか」によって、呼び出しのたびに変わり得ます。

JavaScript
const name = "Global";

const obj = {
  name: "Object",
  show() {
    console.log(name);      // 変数name → スコープで決まる
    console.log(this.name); // this.name → コンテキストで決まる
  }
};

obj.show();
const f = obj.show;
f();

ここでのポイントは次の通りです。

  • nameという変数
    → グローバルに定義されているので、どこから呼んでも"Global"を指す
  • this.name
    obj.show()のときはthis === obj"Object"
    f()のときはthisが変わるのでundefinedや別の値になる

「スコープは定義場所依存、コンテキストは呼び出し方依存」と覚えておくと、かなり整理されてきます。

変数参照はスコープ、this参照はコンテキスト

ここで、一番重要な一句を明確にしておきます。

変数参照はスコープ、this参照はコンテキスト

JavaScript
const value = "global";

const obj = {
  value: "object",
  show: function() {
    console.log(value);
    console.log(this.value);
  }
};

var g = obj.show;
g();

このコードを視覚的に整理してみましょう。

このように、同じ関数の中でも、変数とthisでは「決まり方」が違うことが分かります。

具体例で比較する

スコープとコンテキストを、一つのコードで同時に比較してみます。

JavaScript
const outer = "outer";

const obj = {
  outer: "objOuter",
  inner: "objInner",
  show() {
    const inner = "localInner";
    console.log("outer (var):", outer);
    console.log("inner (var):", inner);
    console.log("this.outer :", this.outer);
    console.log("this.inner :", this.inner);
  }
};

obj.show();
const h = obj.show;
h();

実行結果のイメージは次のようになります。

呼び出し方outer(変数)inner(変数)this.outerthis.inner
obj.show()“outer”“localInner”“objOuter”“objInner”
h()“outer”“localInner”undefined などundefined など
  • outer, innerは、どちらの呼び出し方でも同じ値
    → スコープ(定義場所)で決まるため
  • this.outer, this.innerは、呼び出し方で変わる
    → コンテキスト(呼び出し方)で決まるため

このように一つのテーブルに並べると、スコープとコンテキストは別物であることがよりはっきり見えてきます。

初学者がつまずきやすいポイントと克服のコツ

スコープチェーンとレキシカルスコープのつまずき

スコープは単発で存在するだけでなく、入れ子状に重なって「スコープチェーン」を作るのが普通です。

JavaScriptはレキシカルスコープ(静的スコープ)と呼ばれるルールを採用していて、変数がどこから探されるかは、コードの“書かれている位置”で決まります

JavaScript
const x = 1;

function outer() {
  const x = 2;
  function inner() {
    const x = 3;
    console.log(x);
  }
  inner();
}

outer(); // 3

このときinnerの中でxを参照すると、次の順番で探されます。

  1. inner関数の中にxがあるか
  2. outer関数の中にxがあるか
  3. グローバルにxがあるか

克服のコツは「関数が“どこで定義されたか”を見て、外側に向かって変数を探すイメージを持つこと」です。

「どこから呼ばれたか」ではなく「どこに書かれているか」が重要である、という点はコンテキストと対照的です。

コールバックやイベントでのコンテキストの罠

コールバック関数やイベントハンドラになると、コンテキストの罠にはまりやすくなります。

JavaScript
const button = document.querySelector("#btn");

const obj = {
  message: "clicked!",
  handleClick() {
    console.log(this.message);
  }
};

// NGパターン
button.addEventListener("click", obj.handleClick);

この場合、クリックされたときにhandleClickが呼ばれますが、コンテキストはobjではなくbutton要素になります。

そのためthis.messageundefinedになってしまいます。

克服のコツとしては、次のようなパターンを覚えておくと良いです。

  • DOMイベントのコールバック: this はイベント発生元の要素になる
  • タイマー(setTimeoutなど)のコールバック: 通常の関数ならグローバル/undefined、アロー関数なら外側のthisを引き継ぐ

「コールバックに渡した瞬間、もとのオブジェクトとの“紐付け”が切れる可能性がある」と意識することが重要です。

アロー関数と通常の関数で変わるコンテキスト

アロー関数(() => {})は、コンテキストの扱いにおいて通常のfunctionと大きく違います

  • 通常の関数: 呼び出し方によってthisが決まる
  • アロー関数: 自分自身ではthisを決めず、定義された場所のthisをそのまま「引き継ぐ」
JavaScript
const obj = {
  value: 1,
  normal() {
    console.log("normal this.value:", this.value);
  },
  arrow: () => {
    console.log("arrow this.value:", this.value);
  }
};

obj.normal(); // 1
obj.arrow();  // undefined など (グローバルのthisに依存)

normalは「objのメソッドとして呼ばれる」ためthisがobjになりますが、arrow定義されたときのthisを使います。

上の例ではグローバルスコープで定義されているので、thisはグローバルオブジェクト(またはundefined)になります。

この性質は、先ほどのイベントハンドラのパターンを安全に書くときにも役立ちます。

JavaScript
const obj = {
  message: "clicked!",
  init(button) {
    button.addEventListener("click", () => {
      console.log(this.message); // thisはobjのまま
    });
  }
};

ここではアロー関数の中のthisは「initメソッドが呼ばれたときのthis」を引き継ぐため、常にobjを指すことになります。

スコープとコンテキストを意識したコードの読み方・練習方法

最後に、スコープとコンテキストの理解を深めるための実践的なコツをまとめます。

  1. コードを読むときは、まず「どこで定義されているか」を確認する
    1. 変数や関数が、どのスコープに属しているかを意識する
    2. スコープチェーンの外側に向かって、どの値が見えるかを追ってみる
  2. 次に、「どう呼ばれているか」を一つ一つチェックする
    1. obj.method() なのか、fn() なのか、fn.call(x) なのか
    2. コールバックに渡された関数は、誰が呼ぶのか(ブラウザ? ライブラリ? 自分のコード?)を考える
  3. 小さな実験コードを書いてthisとスコープを確認する
    1. console.log(this) や、変数の値を複数箇所で出力してみる
    2. 同じ関数を違う呼び出し方で実行して、結果を比較する

この「定義場所 → 呼び出し方 → 実験」のサイクルを回していくことで、スコープとコンテキストへの感覚がだんだんと身についていきます。

まとめ

本記事では、初学者が混乱しがちなスコープコンテキストについて、以下のポイントを中心に整理しました。

  • スコープは「変数や関数がどこから見えるか」を決める有効範囲であり、定義場所(レキシカルスコープ)によって決定されること
  • コンテキストは「関数が誰として実行されているか」を表し、thisが何を指すかという形で表れること
  • 変数参照はスコープ、this参照はコンテキストという明確な区別を持つこと
  • スコープチェーンやレキシカルスコープ、コールバックやアロー関数などの文脈で、スコープとコンテキストがそれぞれどう振る舞うか

スコープとコンテキストは、オブジェクト指向や非同期処理、フレームワークのコードを理解する上での土台になります。

最初は難しく感じるかもしれませんが、「どこで定義されたか」と「どう呼ばれたか」を分けて考えるクセをつけ、小さなサンプルコードで挙動を確認していけば、必ず腑に落ちる瞬間がやってきます。

焦らず一歩ずつ手を動かしながら、スコープとコンテキストへの理解を深めていきましょう。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!