JSの基本_閉鎖

1. 変数のスコープ

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

変数には、グローバル変数とローカル変数の 2 つのスコープがあります。

Javascript 言語の特別な機能は、グローバル変数を関数内で直接読み取ることができることです。

jsコード

var n=999;

function f1(){

 alert(n);

}

f1(); // 999

一方、関数内のローカル変数は、当然のことながら関数の外部から読み取ることはできません。

jsコード

​​​​​​​function f1(){

  var n=999;

}
alert(n); // error

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

jsコード

 
  1. function f1(){

  2.     n=999;

  3.   }

  4.  
  5.   f1();

  6.  
  7.   alert(n); // 999

-------------------------------------------------- -------------------------------------------------- ----

 

次に、ローカル変数を外部から読み取るにはどうすればよいでしょうか?

さまざまな理由により、関数内でローカル変数を取得する必要がある場合があります。ただし、前述したように、通常の状況ではこれを行うことはできず、回避策によってのみ実現できます。

つまり、関数内で別の関数を定義します。

jsコード

 

 
  1. function f1(){

  2.  
  3.     n=999;

  4.  
  5.     function f2(){

  6.       alert(n); // 999

  7.     }

  8.  
  9.   }

上記のコードでは、関数 f2 が関数 f1 に含まれていますが、このとき、f1 内のすべてのローカル変数が f2 から見えます。しかし、その逆は不可能です。f2 内のローカル変数は f1 には見えません。これは Javascript 言語独自の「チェーン スコープ」構造で、子オブジェクトは親オブジェクトのすべての変数をレベルごとに検索します。したがって、親オブジェクトのすべての変数は子オブジェクトから参照でき、その逆も同様です。f2 は f1 のローカル変数を読み取ることができるため、f2 が戻り値として使用される限り、f1 の外部でその内部変数を読み取ることはできません。

jsコード

 
  1. function f1(){

  2.  
  3.     n=999;

  4.  
  5.     function f2(){

  6.       alert(n);

  7.     }

  8.  
  9.     return f2;

  10.  
  11.   }

  12.  
  13.   var result=f1();

  14.  
  15.   result(); // 999

-------------------------------------------------- -------------------------------------------------- ----

3. クロージャの概念

コードの前のセクションの f2 関数はクロージャです。

さまざまな専門文献における「閉鎖」の定義は非常に抽象的で、理解するのが困難です。私の理解では、クロージャーは他の関数内の変数を読み取ることができる関数です。Javascript言語ではローカル変数を読み込めるのは関数内のサブ関数のみなので、クロージャは単純に「関数内で定義された関数」と理解できます。したがって、本質的に、クロージャは関数の内部と関数の外部を接続するブリッジです。

-------------------------------------------------- -------------------------------------------------- ----

4 番目に、クロージャの使用

クロージャーはさまざまな場所で使用できます。最大の用途は 2 つあり、1 つは前述の変数を関数内で読み取ることができること、もう 1 つはこれらの変数の値を常にメモリに保持することです。

この文をどう理解すればいいでしょうか?以下のコードを参照してください。

 
  1. function f1(){

  2.  
  3.     var n=999;

  4.  
  5.     nAdd=function(){n+=1}

  6.  
  7.     function f2(){

  8.       alert(n);

  9.     }

  10.  
  11.     return f2;

  12.  
  13.   }

  14.  
  15.   var result=f1();

  16.  
  17.   result(); // 999

  18.  
  19.   nAdd();

  20.  
  21.   result(); // 1000

このコードでは、result は実際にはクロージャ f2 関数です。これは 2 回実行され、1 回目の値は 999、2 回目の値は 1000 です。これは、関数 f1 のローカル変数 n がメモリ内に保持されており、f1 が呼び出された後に自動的にクリアされていないことを証明します。

なぜそうなるのでしょうか? その理由は、f1 が f2 の親関数であり、f2 がグローバル変数に割り当てられているため、f2 は常にメモリ内に存在し、f2 の存在は f1 に依存するため、f1 は呼び出し後ではなく常にメモリ内にあるためです。 、ガベージ コレクション メカニズム (ガベージ コレクション) によってリサイクルされます。

このコードのもう 1 つの注目すべき場所は、「nAdd=function(){n+=1}」という行です。まず、var キーワードが nAdd の前で使用されていないため、nAdd はローカル変数ではなくグローバル変数です。次に、nAdd の値は匿名関数 (匿名関数) であり、この匿名関数自体もクロージャであるため、nAdd は関数内のローカル変数を関数の外で操作できるセッターと同等です。

-------------------------------------------------- -------------------------------------------------- ---- 

5. クロージャ使用時の注意点

1) クロージャは関数内の変数をメモリに格納するため、メモリの消費量が非常に多いため、クロージャを悪用することはできません。そうしないと、Web ページのパフォーマンスに問題が発生し、IE でメモリ リークが発生する可能性があります。 。解決策は、関数を終了する前に、未使用のローカル変数をすべて削除することです。

2) クロージャは、親関数内の変数の値を親関数の外で変更します。したがって、親関数をオブジェクト (オブジェクト) として使用し、クロージャをそのパブリック メソッド (パブリック メソッド) として使用し、内部変数をそのプライベート プロパティ (プライベート値) として使用する場合は、自由に変更しないように注意する必要があります。親関数内の変数の値。

-------------------------------------------------- -------------------------------------------------- ----

6. 考える質問

次のコードの実行結果を理解できれば、クロージャの動作メカニズムを理解できるはずです。

jsコード 

 
  1. var name = "The Window";

  2.   var object = {

  3.     name : "My Object",

  4.     getNameFunc : function(){

  5.       return function(){

  6.         return this.name;

  7.      };

  8.     }

  9. };

  10. alert(object.getNameFunc()()); //"The Window"

このオブジェクトは、実行時の関数の実行環境に基づいてバインドされることがわかります。グローバル関数では、これは window に等しく、関数がオブジェクトのメソッドとして呼び出される場合、これはそのオブジェクトに等しくなります。ただし、匿名関数の実行環境はグローバルです。各関数が呼び出されるとき、そのアクティブ オブジェクトは自動的に 2 つの特別な変数 (this と argument) を取得します。内部関数がこれら 2 つの変数を検索する場合、アクティブ オブジェクトのみが検索されるため、外部関数に直接アクセスすることはできません。のこれら 2 つの変数。ただし、クロージャがアクセスできる変数の外部スコープに this オブジェクトを格納すると、クロージャがオブジェクトにアクセスできるようになります。次のように:

 
  1. var name = "The Window";  

  2. var object = {    

  3. name: "My Object",

  4. getNameFunc: function() {

  5. var that = this;      

  6. return function() {        

  7. return that.name;     

  8. };    

  9. }

  10. };

  11. alert(object.getNameFunc()()); //"My Object"

ここでは、匿名関数を定義する前に、 this オブジェクトを that という名前の変数に割り当てます。クロージャが定義された後は、クロージャもこの変数にアクセスできます。これは、この変数が包含関数内で特別に宣言された変数であるためです。関数が戻った後でも、それはオブジェクトを参照しているため、object.getNameFunc()() を呼び出すと「My Object」が返されます。(これと引数にも同じ問題が存在します。スコープ内の引数オブ​​ジェクトにアクセスしたい場合は、クロージャがアクセスできる別の変数にオブジェクトへの参照を保存する必要があります。)

-------------------------------------------------- -------------------------------------------------- ----
Javascript クロージャの例

 
  1. function outerFun()

  2. {

  3. var a=0;

  4. function innerFun()

  5. {

  6. a++;

  7. alert(a);

  8. }

  9. }

  10. innerFun()

上記のコードは間違っており、innerFun() のスコープは externalFun() 内にあり、outerFun() の外で呼び出すのは間違っています。

これを次のクロージャに変更します。

jsコード

 
  1. function outerFun()

  2. {

  3. var a=0;

  4. function innerFun()

  5. {

  6. a++;

  7. alert(a);

  8. }

  9. return innerFun; //注意这里

  10. }

  11. var obj=outerFun();

  12. obj(); //结果为1

  13. obj(); //结果为2

  14. var obj2=outerFun();

  15. obj2(); //结果为1

  16. obj2(); //结果为2

クロージャとは:

内部関数が定義されているスコープ外で参照されると、内部関数のクロージャが作成されます。内部関数が外部関数にある変数を参照する場合、外部関数が呼び出されたときに、これらの変数は格納されません。メモリ内にあります。クロージャに必要なため解放されます。

-------------------------------------------------- -------------------------------------------------- ----

別の例を見てみましょう

jsコード

 
  1. function outerFun()

  2. {

  3. var a =0;

  4. alert(a);

  5. }

  6. var a=4;

  7. outerFun();

  8. alert(a);

結果は 0,4 です。これは、関数内で var キーワードが inside outFun() のスコープを維持するために使用されているためです。

次のコードをもう一度見てください。

jsコード

 
  1. function outerFun()

  2. {

  3. //没有var

  4. a =0;

  5. alert(a);

  6. }

  7. var a=4;

  8. outerFun();

  9. alert(a);

結果は 0,0 で、これは非常に奇妙なことですが、なぜでしょうか?

スコープ チェーンは、変数の値を決定できるパスを表す用語です。a=0 が実行されると、var キーワードが使用されていないため、代入操作は var a=4 までのスコープ チェーンに従います。その値を変更します。

-------------------------------------------------- -------------------------------------------------- ----------------------------------------------

1. クロージャとは何ですか?

公式の説明では、「クロージャとは、多くの変数を含む式 (通常は関数) と、これらの変数をバインドする環境であるため、これらの変数も式の一部です。これを直接理解できる人は少ないと思います。一言で言えば
、なぜなら、彼の説明があまりにも学術的だからです。実際、この文は人気があります。JavaScript のすべての関数はクロージャです。しかし、一般的に言えば、ネストされた関数によって生成されたクロージャはより強力であり、ほとんどの場合、私たちが「」と呼ぶものでもあります。次のコードを見てください。

 
  1. function a() {

  2. var i = 0;

  3. function b() { alert(++i); }

  4. return b;

  5. }

  6. var c = a();

  7. c();

このコードには 2 つの特徴があります。

1. 関数 b は関数 a 内にネストされています。

2. 関数 a は関数 b を返します。

参照関係を次の図に示します。

 

このように、var c=a() の実行後、変数 c は実際には関数 b を指し、c() の実行後、ウィンドウがポップアップして i の値 (最初は 1) を表示します。このコードは実際にクロージャを作成しますが、なぜでしょうか? 関数 a の外側の変数 c は関数 a の内側の関数 b を参照するため、つまり次のようになります。

  関数 a 内の関数 b が関数 a の外部の変数によって参照されると、クロージャが作成されます。

  もっとはっきり言いましょう。いわゆる「クロージャ」とは、コンストラクタ本体内に別の関数を対象オブジェクトのメソッド関数として定義し、そのオブジェクトのメソッド関数が外側の関数本体内の一時変数を参照することです。これにより、対象オブジェクトが存続期間中常にメソッドを維持できる限り、その時点で元のコンストラクタ本体が使用していた一時変数の値を間接的に維持することが可能になります。元のコンストラクターの呼び出しが終了し、一時変数の名前がなくなっても、変数の値はターゲット オブジェクトのメソッド内で常に参照され、この方法でのみアクセスできます。同じコンストラクターが再度呼び出された場合でも、新しいオブジェクトとメソッドのみが生成され、新しい一時変数は最後の呼び出しとは独立した新しい値にのみ対応します。

2. クロージャの機能は何ですか?

  つまり、クロージャの機能は、 a が実行されて返された後、 a の内部関数 b の実行が a の変数に依存するため、クロージャは Javascript のガベージ コレクション メカニズム GC が a によって占有されているリソースを再利用するのを防ぐことです。これは、クロージャの機能についての非常に簡単な説明です。専門的でも厳密でもありませんが、おそらくそれを意味します。クロージャを理解するには、段階的なプロセスが必要です。

上記の例では、クロージャの存在により、関数aが戻った後は常にaのiが存在するため、c()が実行されるたびにiはiに1を加えた値になります。

  次に、別の状況を想像してみましょう。a が関数 b を返さない場合、状況はまったく異なります。a が実行された後、b は a の外の世界に戻されず、a によって参照されるだけであり、このとき a は b によってのみ参照されるため、関数 a と b は相互に参照しますが、関数 b は干渉しません。外の世界 (外の世界によって参照される) の場合、関数 a と b は GC によってリサイクルされます。(JavaScriptのガベージコレクションの仕組みについては後ほど詳しく紹介します)

3. クロージャー内の小宇宙

  クロージャと関数 a と入れ子関数 b の関係をより深く理解したい場合は、関数実行環境 (実行コンテキスト)、アクティブ オブジェクト (呼び出しオブジェクト)、スコープ (スコープ)、ロールなど、他のいくつかの概念を導入する必要があります。ドメイン チェーン (スコープ チェーン)。これらの概念を説明するために、関数 a の定義から実行までのプロセスを例として取り上げます。

  1. 関数 a が定義されると、js インタプリタは関数 a のスコープ チェーンを、a が定義されたときに定義された「環境」に設定します。a がグローバル関数の場合、スコープ チェーンにはウィンドウ オブジェクトのみが存在します。
  2. 関数 a が実行されると、関数 a は対応する実行コンテキスト (実行コンテキスト) に入ります。
  3. 実行環境を作成するプロセスでは、まず a 、つまり a のスコープにscope属性が追加され、その値が手順1のスコープチェーンになります。つまり、a.scope=a のスコープ チェーンです。
  4. 次に、実行環境は呼び出しオブジェクトを作成します。アクティブ オブジェクトもプロパティを持つオブジェクトですが、プロトタイプを持たないため、JavaScript コードから直接アクセスすることはできません。アクティブ オブジェクトを作成した後、そのアクティブ オブジェクトを のスコープ チェーンの先頭に追加します。この時点で、a のスコープ チェーンには、a のアクティブ オブジェクトとウィンドウ オブジェクトの 2 つのオブジェクトが含まれています。
  5. 次のステップでは、アクティブ オブジェクトに argument プロパティを追加します。このプロパティには、関数 a を呼び出すときに渡される引数が保持されます。
  6. 最後に、関数 a のすべての仮パラメータと内部関数 b の参照も、a のアクティブ オブジェクトに追加されます。このステップでは関数 b の定義が完了したので、ステップ 3 と同様に、関数 b のスコープチェーンを b が定義されている環境、つまり a のスコープに設定します。

これで関数a全体の定義から実行までのステップが完了します。このとき、a は関数 b の参照を c に返し、関数 b のスコープ チェーンには関数 a のアクティブ オブジェクトへの参照が含まれます。つまり、b は a で定義されたすべての変数と関数にアクセスできます。関数 b は c によって参照されており、関数 b は関数 a に依存しているため、関数 a は復帰後に GC によってリサイクルされません。

関数bを実行すると上記と同様になります。したがって、次の図に示すように、b のスコープ チェーンには、実行中に b のアクティブ オブジェクト、a のアクティブ オブジェクト、およびウィンドウ オブジェクトの 3 つのオブジェクトが含まれます。

図に示すように、関数 b の変数にアクセスする場合、検索順序は次のようになります。

  1. まず自身のアクティブオブジェクトを検索し、存在する場合はリターンし、存在しない場合は関数 a のアクティブオブジェクトを検索し、見つかるまで順番に検索します。
  2. 関数 b にプロトタイプのプロトタイプ オブジェクトがある場合、関数 b は、自身のアクティブ オブジェクトを検索した後、まず自身のプロトタイプ オブジェクトを検索し、その後検索を続けます。これは、JavaScript の変数検索メカニズムです。
  3. スコープ チェーン全体が見つからない場合は、未定義を返します。

要約すると、この段落では、関数の定義と実行という 2 つの重要な単語について説明します。この記事では、関数のスコープは関数の実行時ではなく定義時に決定されると述べられています (手順 1 と 3 を参照)。コードの一部を使用して、この問題を説明します。

 
  1. function f(x) {

  2. var g = function () { return x; }

  3. return g;

  4. }

  5. var h = f(1);

  6. alert(h());

このコードの変数 h は、f の無名関数 (g によって返される) を指します。

  • 関数 h のスコープが、alert(h()) の実行によって決定されるとすると、このときの h のスコープ チェーンは、h のアクティブ オブジェクト -> アラートのアクティブ オブジェクト -> ウィンドウ オブジェクトになります。
  • 関数 h のスコープは定義時に決定されている、つまり h が指す無名関数のスコープは定義時に決定されているとします。実行中、h のスコープ チェーンは、h のアクティブ オブジェクト -> f のアクティブ オブジェクト -> ウィンドウ オブジェクトになります。

最初の仮定が真である場合、出力値は未定義であり、2 番目の仮定が真である場合、出力値は 1 です。

実行結果は 2 番目の仮定が正しいことを証明し、関数のスコープが実際に関数の定義時に決定されることを示しています。
 

4. クロージャの適用シナリオ
関数内の変数を保護します。最初の例を例にとると、関数 a の i には関数 b からのみアクセスでき、他の手段ではアクセスできないため、i のセキュリティは保護されます。

  1. 変数をメモリ内に保持します。前の例と同様に、クロージャのため、関数 a の i は常にメモリ内に存在するため、c() が実行されるたびに、i は 1 ずつインクリメントされます。
  2. 変数のセキュリティを保護することで、JS のプライベート プロパティとプライベート メソッド (外部からアクセスできない) を実現します。
    プライベート プロパティとメソッドはコンストラクターの外部からアクセスできません。

    function Constructor(...) {  
      var that = this;  
      var メンバー名 = 値; 
      関数メンバー名(...) {...}
    }

上記の 3 点はクロージャの最も基本的な適用シナリオであり、多くの古典的なケースはこれに由来します。
 

五、JavaScriptのガベージコレクション機構

Javascript では、オブジェクトが参照されなくなった場合、このオブジェクトは GC によって回収されます。2 つのオブジェクトが相互に参照し、サードパーティによって参照されなくなった場合、相互に参照している 2 つのオブジェクトもリサイクルされます。関数 a は b によって参照され、b は a の外側の c によって参照されるため、関数 a は実行後にリサイクルされません。

 

6. 結論

Javascript クロージャを理解することは、上級 JS プログラマになる唯一の方法であり、その解釈と動作メカニズムを理解することによってのみ、より安全で洗練されたコードを書くことができます。

この記事の参考リンク:  http://www.jb51.net/article/24101.htm

おすすめ

転載: blog.csdn.net/qq_41916378/article/details/109314786