JavaScriptのスコープの違い

JavaScriptのスコープについてまとめました。

スコープとは

スコープとは、実行中のコードから変数や関数などの値と式が参照できる範囲のこと。スコープの内側では参照ができ、スコープの外からスコープに定義されている変数は参照できない。

異なるスコープなら同じ変数名や関数名を定義できる。

スコープの種類

グローバルスコープ

グローバルスコープはプログラム実行時に暗黙的に作られる一番外側のスコープで、グローバルスコープに定義された変数はプログラムのどこからでも参照できる。

const a = 'global'

function fn() {
    
    function fn2() {

        function fn3() {
            // 関数の中の関数の中の関数の中からでも参照できる
            console.log(a) // global
        }

        fn3()
    }

    fn2()
}

fn()

関数スコープ

関数スコープは関数内に作られるスコープ。

function fn() {
    // 関数スコープ
    var a = 0
    let b = 0
    const c = 0
}

// 関数の外から参照するとエラーになる
console.log(a) // Uncaught ReferenceError: a is not defined
console.log(b) // Uncaught ReferenceError: b is not defined
console.log(c) // Uncaught ReferenceError: c is not defined


関数内で定義した関数を外からは参照できない、

function fn() {
    console.log('fn')

    function fn2() {
        console.log('fn2')
    }
}

fn() // fn
fn2() // Uncaught ReferenceError: fn2 is not defined

関数スコープ内に変数を定義することによってグローバルスコープの汚染を防ぐことができるので、関数内でしか使わない変数は関数内に定義する。

ブロックスコープ

ブロックスコープは{}の中のスコープで、if文やwhile文もブロックスコープにあたる。

ブロックスコープ内でvarを使って定義した変数は外から参照できてしまうので、letもしくはconstを使うようにする。

{
    // ブロックスコープ
    var a = 0
    let b = 1
    const c = 2
}

// varで定義した変数は参照できてしまう
console.log(a) // 0

// let、もしくはconstで定義した変数は外から参照できない
console.log(b) // Uncaught ReferenceError: b is not defined
console.log(c) // Uncaught ReferenceError: c is not defined

ブロックスコープ内の関数宣言は外から参照でき、letconstを使った関数式は参照できない。

{
    // 関数宣言
    function fn() {
        console.log('fn')
    }

    // 関数式
    const fn2 = function() {
        console.log('fn2')
    }
}

fn() // fn
fn2() // Uncaught ReferenceError: fn2 is not defined

レキシカルスコープ

レキシカルスコープは、コードを書く場所によって参照できる変数が変わるスコープのこと。実行中のコードから見た外部スコープを指す。

コードが実行されるタイミングではなく、コードを記述したタイミングでスコープが決定される。

変数aを参照する関数fn2を関数fnの中に記述すると参照できる。

function fn() {
    const a = 0

    function fn2() {
        // 外側のスコープに定義されているので参照できる
        console.log(a) // 0
    }

    fn2()
}

fn()

しかし、関数fn2をグローバルスコープで定義して実行するとエラーになる。

function fn() {
    const a = 0
}

fn()

function fn2() {
    // aは別の関数スコープの中に定義されているため参照できない
    console.log(a) // Uncaught ReferenceError: a is not defined
}

fn2()

このようにコードを記述する場所が変わることで参照できる変数も変化する。

また、レキシカルスコープは関数を実行した時点ではなく、定義した時点でスコープが決まる。

const a = 0

function fn() {
    console.log(a) // 0
}

function fn2() {
    const a = 1

    // fnを定義した時点で0を参照しているので、1にならない
    fn()
}

fn()
fn2()

スコープチェーン

スコープチェーンとは、内側のスコープから一番近い外側へのスコープへと順番に参照できる変数を探す仕組みのこと。

別のスコープで同じ名前の変数が定義されている場合は、内側から優先して参照される。

// グローバルスコープ
const a = 'global'

// ブロックスコープ
{
    const a = 'block'

    // 一番近いblockを参照
    console.log(a) // block

    // 関数スコープ
    function fn() {
        const a = 'function'

        // 一番近いfunctionを参照
        console.log(a) // function
    }

    fn()

    // 関数スコープは無視して一番近いblockを参照
    console.log(a) // block
}

// ブロックスコープは無視して一番近いglobalを参照
console.log(a) // global

まとめ

  • グローバルスコープにはなるべく変数を定義しない
  • 内側のスコープから一番近い外側のスコープから順番に参照できる変数を探していき、なければ未定義のエラーを返す