クロージャは JavaScript の重要な機能であり、その最大の役割は関数の実行中に情報を保存することです。 JavaScript では、クロージャの多くの機能は、関数呼び出し中のスコープ チェーンから発生します。
関数呼び出しオブジェクトと変数のスコープ チェーン
JavaScript の関数呼び出しごとに、JavaScript は関数内で定義されたローカル変数を保存するローカル オブジェクトを作成します。関数内にネストされた関数が定義されている場合、JavaScript はすでに作成されているオブジェクトの上にネストされたローカル オブジェクトを定義します。定義されたローカル オブジェクト。関数の場合、その内部にネストされた関数定義の層と同じ数のネストされたローカル オブジェクトの層が存在します。このローカルオブジェクトを「関数呼び出しオブジェクト」と呼びます(ECMAScript 3では「呼び出しオブジェクト」、ECMAScript 5では「宣言型環境レコード」に名前変更されましたが、個人的にはECMAScript 3の名前のほうがわかりやすいと思います)。次の関数呼び出しを例に挙げます:
この簡単な例では、f() 関数が呼び出されると、JavaScript は f() 関数の呼び出しオブジェクト (f_invokeObj と呼びます) を作成します。f_invokeObj オブジェクト内には、a と x という 2 つの属性があります。 f() を実行すると、a の値は 10、x の値は 6 になるため、最終的な戻り結果は 60 になります。アイコンは次のとおりです:
関数のネストが存在する場合、JavaScript は複数の関数呼び出しオブジェクトを作成します:
この例では、f() 関数が呼び出されると、JavaScript は f() 関数の呼び出しオブジェクト (f_invokeObj) を作成します。このオブジェクトには 2 つの内部属性 a と x があり、a の値は 10、x の値は です。 6; f () を実行すると、JavaScript は f() 関数内で g() 関数を解析して定義し、内部属性 b を持つ g() の呼び出しオブジェクト (g_invokeObj) を作成します。b の値は受信パラメータ x と同じ 6 であるため、最終的な戻り結果は 360 になります。アイコンは次のとおりです:
ご覧のとおり、関数呼び出しオブジェクトはチェーンを形成しています。埋め込み関数 g() が実行されており、変数値を取得する必要がある場合、最も近い関数呼び出しオブジェクトから検索が開始され、それが検索できない場合は、関数呼び出しオブジェクト チェーンに沿ってさらに呼び出しオブジェクトが検索されます。いわゆる「可変スコープチェーン」。同じ変数が 2 つの関数呼び出しオブジェクトに出現する場合、関数はそれ自体に最も近い呼び出しオブジェクトの変数値を取得します:
上記の例では、g()関数の呼び出しオブジェクト(g_invokeObj)とf()関数の呼び出しオブジェクト(f_invokeObj)の両方に変数aが存在し、g()関数のaの値が異なります。 run、g() 関数内で使用される a の値は 1 ですが、g() 関数外で使用される a の値は 10 です。図に示す関数呼び出しオブジェクト チェーンは次のとおりです。
クロージャとは何ですか?
JavaScript のすべての関数 (関数) はオブジェクトであり、関数が定義されると、対応する関数呼び出しオブジェクト チェーンが生成されます。関数定義は、関数呼び出しオブジェクト チェーンに対応します。関数オブジェクトが存在する限り、対応する関数呼び出しオブジェクトが存在します。関数が使用されなくなると、対応する関数呼び出しオブジェクトはガベージ コレクションされます。関数オブジェクトと関数呼び出しオブジェクトは 1 対 1 で結合されます。チェーン、単に「閉鎖」と呼んでください。 f() 関数と g() 関数の上記の例では、2 つのクロージャがあります。f() 関数オブジェクトと f_invokeObj オブジェクトがクロージャを形成し、g() 関数オブジェクトと g_invokeObj-f_invokeObj オブジェクト チェーンが形成されます。一緒に形成します 2番目のクロージャ。 g() 関数の実行が終了すると、g() 関数は使用されなくなるため、g() クロージャはガベージ コレクションされます。その後、f() 関数の実行が終了すると、同じ理由で f() が閉じられます。パッケージもガベージコレクションされました。
クロージャの定義から、すべての JavaScript 関数は定義後はクロージャであると結論付けることができます。すべての関数はオブジェクトであるため、すべての関数は実行後に対応する呼び出しオブジェクト チェーンも持ちます。
ただし、クロージャが真価を発揮するのは、入れ子関数の場合です。インライン関数は外部関数の実行時に定義されるため、インライン関数のクロージャに保存される変数値(特に外部関数のローカル変数値)は、この実行時の値となります。埋め込み関数オブジェクトが存在する限り、そのクロージャは引き続き存在します (クロージャ内の変数値は変更されません)。したがって、関数の実行プロセスに関する情報を保存するという目的は達成されます。次の例を考えてみましょう:
この例では、f() 関数が実行されると、g() 関数が定義され、g() 関数のクロージャが作成され、g_invokeObj-f_invokeObj オブジェクト チェーンが含まれるため、f() が保存されます。関数実行時の変数 a の値。 console.log() ステートメントが実行されると、g 関数オブジェクトがまだ存在するため、g() クロージャーもまだ存在します。まだ存在する g 関数オブジェクトを実行すると、JavaScript はまだ存在する g() クロージャーを使用して Get します。そこから変数 a (「内部」) の値を取得します。