とある角度から

お腹いっぱいたべられる幸せ

クロージャとスコープチェーン

クロージャをなるべく簡潔にまとめてみます。

まず、クロージャを正しく理解するためには以下の理解が不可欠なので、その説明をします。

  • javascriptのスコープ
  • スコープチェーン
  • スコープは関数実行時ではなく、関数定義時に決定

javascriptのスコープ

そもそもスコープとは、ある変数や関数が特定の名前で参照される範囲のことです。
で、javascriptのスコープはというと、以下の3種類に分けられます。

  1. グローバルスコープ
  2. 関数スコープ(=ローカルスコープ)
  3. evalスコープ

3.は置いといて、1,2についてサンプルを交えて説明

var foo = 0; // グローバルスコープで定義 
console.log(foo); // 出力:0 
var myFunction1 = function(){ 
    var foo1 = 1; // 関数スコープ(myFunction1)で定義 
    console.log(foo1); // 出力:1 

    // 入れ子な関数 
    var myFunction2 = function() { 
        var foo2 = 2; // 関数スコープ(myFunction2)で定義 
        console.log(foo2); // 出力:2 
    }(); 
    //console.log(foo2); // エラー 
}();
//console.log(foo1); // エラー 

1行目のfooはグローバルスコープに定義された変数で、ソースのどこからでも参照可能です。
foo1,foo2はそれぞれの関数スコープ内で定義されており、その関数内であれば参照が可能ですが、関数外(12,14行目)では参照できません。

スコープチェーン

では、上記のmyFunction1内の入れ子な関数myFunction2内から、foo1は参照できるのでしょうか?

var foo = 0; // グローバルスコープで定義
console.log(foo); // 出力:0
var myFunction1 = function(){
    var foo1 = 1; // 関数スコープ(myFunction1)で定義
    console.log(foo1); // 出力:1
    
    // 入れ子な関数
    var myFunction2 = function() {
        var foo2 = 2; // 関数スコープ(myFunction2)で定義
        console.log(foo2); // 出力:2
        console.log(foo1); // 追加 出力:1
        console.log(foo); // 追加 出力:0
    }();
    //console.log(foo2); // エラー
}();
//console.log(foo1); // エラー

11,12行目でfoo1,fooにアクセスしてみると参照できました。
javascriptでは現在の関数スコープ内に指定された変数がない場合、その親関数の関数スコープ内を探しにいきます。
そして親関数にも変数がない場合には、さらにその親関数を探しにいき、最終的にはグローバルスコープに定義された変数まで確認します。
これをスコープチェーンと呼びます。
グローバルスコープに定義された変数がソースのどこからでも参照可能なのは、スコープチェーンの仕組みがあるからです。

スコープは関数実行時ではなく、関数定義時に決定

上記の関数スコープは実行時に変わることは一切ありません。
例えば、myFunction2が他の関数から参照され、そこで呼び出されたとしても、myFunction2内の変数の解釈はmyFunction2関数スコープ→myFunction1関数スコープ→グローバルスコープというルートを辿って検索されます。
つまり、myFunction2の中からは常にmyFunction1の変数が参照できるということです。
これは、たとえ無名関数であっても同様です。

改めてクロージャとは

改めて、クロージャのサンプルをみてみます。

var myFunction = (function(){
	var foo = 'foo';
	return function(){
		return foo;
	}
})();
console.log(myFunction()); // 出力:foo

3行目で、関数の戻り値として定義されている無名関数は、スコープチェーンにより、親関数内で定義されているfooにアクセスすることができます。
myFunctionにはこの関数が代入され、またスコープは実行時に変わることはないため、変数fooはこのmyFunctionを通じてしかアクセスすることができなくなってしまいました。
つまり、閉じ込められてしまいました。これがクロージャです。

。。。はいこれがクロージャです。。。と言われてもピンとこないですよね。。。
言い換えます。これがクロージャの仕組みです。
クロージャを使ってどんなことができるのかは別の機会に。。。