🛸

JavaScriptのスコープ

2025/02/07に公開

スコープとは

変数や関数が参照・使用できる範囲のこと。

スコープの種類

グローバルスコープ

どの関数やブロックにも属さないトップレベルのスコープ。
関数やブロック外で宣言するとグローバルスコープとなる。
スクリプト内のどこからでもアクセス可能。

// グローバルスコープの変数
const a = 10;

// グローバルスコープの関数
function example() {
    console.log(a); // 10
}
example();

ローカルスコープ(関数スコープ)

宣言された関数内のみでアクセス可能なスコープ。
関数(function)内で宣言すると、ローカルスコープ(関数スコープ)となる。

function test() {
    const x = 10;
}
console.log(x); // Uncaught ReferenceError: x is not defined

ブロックスコープ

ブロック {} 内のみで有効。letconst により作成される。

if (true) {
    let a = 10;
    const b = 20;
}
console.log(a); // Uncaught ReferenceError: a is not defined

ブロックスコープのメリット

varではループごとに新しいスコープが作成されないため、非同期処理で意図しない動作が発生する。

// varの場合
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // 3が3回出力される
}
// letの場合
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // 0,1,2が出力

レキシカルスコープ(静的スコープ)

関数が定義された位置によって決まるスコープ

function outer() {
    let x = "outer";
    function inner() {
        console.log(x); // "outer"
    }
    inner();
}

モジュールスコープ(import /export

ES6で追加されたESモジュールは、トップレベルの変数がグローバルオブジェクトに追加されない。
そのためJavaScriptファイル間で変数の競合を防ぐことができる。
ESモジュールは<script type="module">またはモジュールバンドラー利用時に適用される。

// module.js
const secret = "内部の変数"; // 外部からアクセス不可
export const publicValue = "外部に公開";

// main.js
import { publicValue } from './module.js';
console.log(publicValue); // "外部に公開"
console.log(secret); // secret is not defined

var / let / const のスコープの違い

var

  • グローバルスコープで宣言した場合、グローバルオブジェクトに登録される。
  • 関数内で宣言した場合、ローカルスコープ(関数スコープ)に束縛される。
  • ifforなどのブロック内で宣言をした場合、スコープが作られない。
  • 再宣言可能。
// グローバルスコープ
var a = 10;

function example() {
    console.log(window.a); // 10(ブラウザのグローバルオブジェクト)
}

window.example(); // (ブラウザのグローバルオブジェクト)

let /const

  • ブロックスコープを作るため、ブロック内でのみアクセス可能。
  • 再宣言不可。
function testVar() {
    if (true) {
        var a = 10; // 関数スコープ
    }
    console.log(a); // 10 (ブロックの外でもアクセス可能)
}

function testLet() {
    if (true) {
        let b = 20; // ブロックスコープ
    }
    console.log(b); // ReferenceError: b is not defined
}

IIFE(Immediately Invoked Function Expression)

グローバルスコープを汚染しないための記法。
前述の通り、varは、ifforなどのブロックをスコープとして認識しないため、例えば以下のような動作になる。

for (var i = 0; i < 3; i++) {
    console.log(i);
}
console.log(i); // 3

この問題を回避するために、以前はIIFE(即時関数)が使われていた。

(function() {
    var secret = "hidden";
    console.log(secret);
})();
console.log(secret); // Uncaught ReferenceError: secret is not defined

現在はletconstでブロックスコープを作れるので、IIFEはあまり使われなくなった。


スコープの動作

スコープチェーン

スコープチェーンとは、JavaScriptが変数を探索する仕組みのこと。
JavaScriptは変数を参照するときに「内側 → 外側」へ探索する。
最終的にグローバルスコープまで到達しても見つからなければReferenceError になる。

let x = 10; // グローバルスコープ

function outer() {
    let y = 20; // outer のスコープ

    function inner() {
        let z = 30; // inner のスコープ
        console.log(x, y, z); // 10 20 30
    }

    inner();
}

outer();

上記の例では、inner() のスコープにはxがないため、スコープチェーンをたどって outer() のスコープ、次にグローバルスコープを探す。

変数シャドウイング

内側のスコープに同じ名前の変数がある場合、外側の変数にアクセスできなくなる。

let x = "global";
function outer() {
  let x = "outer";
  function inner() {
    console.log(x); // "outer"(内側のスコープ優先)
  }
  inner();
}
outer();

クロージャ

クロージャとは、外部スコープの変数を記憶し続ける内部関数のこと。

クロージャのメリット

関数の外部からはアクセスできない変数を保持したまま関数を実行できる。
データのカプセル化(プライベート変数の実現)などに役立つ。

function counter() {
    let count = 0; // 外部スコープの変数(プライベート変数となる)
    
    return function () { // 内部関数(クロージャ)
        count++;
        console.log(count);
    };
}

const increment = counter(); // 内部関数を取得し、新しいカウンターを作成
increment(); // 1
increment(); // 2(countの値が保持され続ける)

Discussion