ホームページ > 記事 > ウェブフロントエンド > JS学習におけるクロージャの深い理解
クロージャは、特にプログラミングの基礎がない人にとって、js を理解するのが比較的難しい点です。
実際、クロージャに関して注意すべき点はいくつかありますが、それらをすべて理解していれば、克服することは難しくありません。クロージャの基本原則についてお話しましょう。
クロージャの概念
クロージャは、関数と、作成された関数内のスコープ オブジェクトを組み合わせたものです。 (スコープ オブジェクトについては後述します)
もっと簡単に言うと、「1 つ以上の関数が関数内にネストされている限り、それらをクロージャと呼ぶことができます。
これと同様に:
function A() { var i = 5; return function() { console.log('i = '+i); } } var a = A(); a(); // i = 5
」
クロージャの原則
1. 外部関数のローカル変数がクロージャ関数によって呼び出された場合、外部関数の実行直後には再利用されません。
どの言語であっても、オペレーティング システムには、メモリを削減するために余分に割り当てられた領域をリサイクルするガベージ コレクション メカニズムがあることがわかっています。関数のライフサイクルは、関数が呼び出されたときに開始され、関数呼び出しが完了すると、関数内のローカル変数がリサイクル メカニズムによってリサイクルされます。
外部関数 A が呼び出されるとき、A のローカル変数 i はオペレーティング システムによってリサイクルされ、存在しません。ただし、クロージャを使用すると、結果は次のようになりません。それはリサイクルされません。想像してみてください、i がリサイクルされた場合、返された関数は unknown と出力されませんか?
i なぜリサイクルされないのですか?
JavaScript が関数を実行すると、スコープ オブジェクトが作成され、関数内のローカル変数 (関数の仮パラメーターもローカル変数です) がその中に保存され、関数に渡された変数が一緒に初期化されます。
A が呼び出されるとき、スコープ オブジェクトが作成されます。これを Aa と呼びます。この Aa は次のようになります。 Aa { i: 5 }; A 関数が関数を返した後、 A が実行されます。 Aa オブジェクトはリサイクルされるはずですが、返された関数は Aa の属性 i を使用しているため、返された関数は Aa への参照を保存するため、Aa はリサイクルされません。
したがって、スコープ オブジェクトを理解することで、クロージャに遭遇したときに関数呼び出しが完了したときに関数のローカル変数がすぐにリサイクルされない理由を理解することができます。
別の例:
function A(age) { var name = 'wind'; var sayHello = function() { console.log('hello, '+name+', you are '+age+' years old!'); }; return sayHello; } var wind = A(20); wind(); // hello, wind, you are 20 years old!
そのスコープ オブジェクト Ww が何かわかりますか?
Ww{ age: 20; name: 'wind'; };
2. 外部関数が呼び出されるたびに、新しいクロージャーが生成され、以前のクロージャーは引き続き存在し、相互に影響しません。
3. 同じクロージャは最後の状態を保持し、再度呼び出されるときは前回に基づきます。
外部関数が呼び出されるたびに生成されるスコープ オブジェクトは異なると考えることができます。上記の例では、渡すパラメータ age が毎回異なるため、生成されるオブジェクトも毎回異なります。
外部関数が呼び出されるたびに、新しいスコープ オブジェクトが生成されます。
function A() { var num = 42; return function() { console.log(num++); } } var a = A(); a(); // 42 a(); // 43 var b = A(); // 重新调用A(),形成新闭包 b(); // 42
このコードにより、2 つのことがわかります。まず、a(); を 2 回連続して呼び出すと、num が元の基準でインクリメントされます。これは、同じクロージャが最後の状態を保持し、再度呼び出されるときは前回に基づくことを意味します。 2. b() の結果は 42 で、これは新しいクロージャであり、他のクロージャの影響を受けないことを示しています。
シャボン玉を吹くのと同じように、シャボン玉を吹く(外部関数を呼び出す)たびに、新しいシャボン玉(閉鎖)が同時に生成されると考えることができます。 2 つのシャボン玉は同時に存在できますが、互いに影響を与えることはありません。
4. 外部関数に存在する複数の関数は「生きて死ぬ」
以下の3つの関数は同時に宣言され、スコープオブジェクトのプロパティ(ローカル変数)にアクセスして操作することができます。
var fun1, fun2, fun3; function A() { var num = 42; fun1 = function() { console.log(num); } fun2 = function() { num++; } fun3 = function() { num--; } } A(); fun1(); // 42 fun2(); fun2(); fun1(); // 44 fun3(); fun1(); //43 var old = fun1; A(); fun1(); // 42 old(); // 43 上一个闭包的fun1()
関数は複数の戻り値を持つことができないので、グローバル変数を使用しました。もう一度、A() を 2 回呼び出すときに新しいクロージャが作成されることがわかります。
クロージャがループ変数に遭遇したとき
クロージャについて話すときは、次のコードを見てください:
function buildArr(arr) { var result = []; for (var i = 0; i < arr.length; i++) { var item = 'item' + i; result.push( function() {console.log(item + ' ' + arr[i])} ); } return result; } var fnlist = buildArr([1,2,3]); fnlist[0](); // item2 undefined fnlist[1](); // item2 undefined fnlist[2](); // item2 undefined
どうしてこんなことが起こるのでしょうか?私たちが想定する 3 つの出力は、item0 1、item1 2、item2 3 となるはずです。返された結果の配列に未定義の item2 が 3 つ格納されているのはなぜですか?
クロージャがループ変数に遭遇すると、ループ終了後に変数値が均一に保存されることがわかります。ループ終了時には i はループ変数であり、i++ の後にはたまたま i が 3 になります。 arr [3] には値がないため、未定義です。「なぜ item2 の値は item3 ではないのですか?」と疑問に思う人もいるかもしれません。なお、最後のループ、つまり、i = 2の場合、itemの値はitem2となり、i++、i = 3の場合、ループ条件が成立せず、この時点でのitemの値が確定する。 , したがって、この時点では arr[i] は arr[3] で、item は item2 です。これは理解できますか?コードを次のように変更すると意味がわかります:
function buildArr(arr) { var result = []; for (var i = 0; i < arr.length; i++) { result.push( function() {console.log('item' + i + ' ' + arr[i])} ); } return result; } var fnlist = buildArr([1,2,3]); fnlist[1](); // item3 undefined
そこで問題は、それをどのように修正するかです。コードを見てみましょう:
function buildArr(arr) { var result = []; for (var i = 0; i < arr.length; i++) { result.push( (function(n) { return function() { var item = 'item' + n; console.log(item + ' ' + arr[n]); } })(i)); } return result; } var fnlist = buildArr([1,2,3]); fnlist[0](); // item0 1 fnlist[1](); // item1 2 fnlist[2](); // item2 3
自己実行関数を使用して i をバインドすると、i のすべての状態が保存され、答えは期待したものと同じになります。
そのため、将来クロージャを使用するときにループ変数に遭遇した場合は、自己実行関数を使用してそれをバインドすることを習慣的に考える必要があります。
上記はクロージャについての私の理解です。ご意見やご提案がございましたら、コメント欄でさらにお知らせいただければ幸いです。お互いに感謝し、励まし合いましょう。