クロージャ(closure)は JavasSript の大きな難所であり、その特色でもあります。多くの高度な応用はクロージャに依存して実現されています。

1. 変数のスコープ

クロージャを理解するには、まず JavasSript の特殊な変数スコープを理解する必要があります。

変数のスコープは2種類しかありません。グローバル変数とローカル変数です。

JavasSript 言語の特別な点は、関数の内部ではグローバル変数を直接読み取ることができますが、関数の外部では関数内部のローカル変数を読み取ることができないことです。

注意点:関数内部で変数を宣言するときは、必ず var コマンドを使用する必要があります。使用しない場合、実際にはグローバル変数を宣言していることになります。

2. 外部から関数内部のローカル変数を読み取るには?

さまざまな理由から、関数内部のローカル変数を取得する必要がある場合があります。しかし、上で述べたように、通常はこれは不可能です。別の方法によってのみ実現できます。

それは、関数の内部でさらに関数を定義することです。

function f1(){
    var n=999;
    function f2(){
        alert(n); // 999
    }
}

上記のコードでは、関数 f2 は関数 f1 の内部に含まれています。このとき、f1 内部のすべてのローカル変数は f2 から見えます。しかし、逆はできません。f2 内部のローカル変数は f1 からは見えません。

これが JavasSript 言語に特有の「チェーンスコープ」構造(chain scope)です。

子オブジェクトは親オブジェクトの変数を階層をたどって上に向かって探します。したがって、親オブジェクトのすべての変数は子オブジェクトから見えますが、逆は成り立ちません。

f2 が f1 のローカル変数を読み取れるなら、f2 を戻り値として返せば、f1 の外部でその内部変数を読み取れるではありませんか!

3. クロージャの概念

上記コードの f2 関数がクロージャです。

さまざまな専門文献でのクロージャの定義は非常に抽象的ですが、私の理解では、クロージャとは他の関数の内部変数を読み取ることができる関数です。

JavasSript では、関数内部の子関数だけがローカル変数を読み取れるため、クロージャは「関数内部に定義された関数」と簡単に理解できます。

したがって、本質的に、クロージャは関数内部と関数外部を結ぶ橋です。

4. クロージャの用途

クロージャは多くの場面で使用できます。その最大の用途は2つあります。1つは前述の関数内部の変数を読み取れること、もう1つはこれらの変数の値を常にメモリに保持させ、f1 の呼び出し後に自動的にクリアされないようにすることです。

なぜそうなるのでしょうか?その理由は、f1 は f2 の親関数であり、f2 がグローバル変数に代入されるため、f2 は常にメモリ内に存在し、f2 の存在は f1 に依存しているので、f1 も常にメモリ内にあり、呼び出し終了後にガベージコレクション(garbage collection)によって回収されることはありません。

このコードでもう1つ注目すべき点は、すなわち"nAdd=function(){n+=1}"この行では、まず nAdd の前に var キーワードを使用していないため、nAdd はローカル変数ではなくグローバル変数です。次に、nAdd の値は匿名関数(anonymous function)であり、この匿名関数自体もクロージャなので、nAdd はセッターに相当し、関数の外部から関数内部のローカル変数を操作できます。

5. クロージャを使用する際の注意点

  • クロージャは関数内の変数をすべてメモリに保持するため、メモリ消費が大きくなります。そのため、クロージャを濫用してはいけません。濫用するとウェブページのパフォーマンス問題を引き起こし、IEではメモリリークを引き起こす可能性があります。解決策は、関数を終了する前に、使用しないローカル変数をすべて削除することです。
  • (2) クロージャは親関数の外部で、親関数内部の変数の値を変更します。したがって、親関数をオブジェクト(object)として使用し、クロージャをそのパブリックメソッド(Public Method)、内部変数をそのプライベート属性(private value)として扱う場合は、親関数内部の変数の値をむやみに変更しないよう十分注意する必要があります。

原文リンク: https://www.cnblogs.com/cxying93/p/6103375.html