上記の例では、「fooClosure」の部分が擬似コードです。同様に、ECMAScript では、「foo」関数には、関数のコンテキストを作成するスコープ チェーンという内部プロパティがすでにあります。
「語彙」は通常省略されます。上の例は、クロージャの作成時にコンテキスト データが保存されることを強調しています。次回関数が呼び出されるとき、自由変数は保存された (クロージャ) コンテキストで見つかり、上記のコードに示されているように、変数 "z" の値は常に 10 になります。
定義では「コード ブロック」というより広い用語を使用しますが、通常 (ECMAScript では) よく使用する関数を使用します。もちろん、クロージャのすべての実装がクロージャと関数を結びつけるわけではありません。たとえば、Ruby 言語では、クロージャはプロシージャ オブジェクト、ラムダ式、またはコード ブロックである可能性があります。
コンテキストが破棄された後にローカル変数を保存するという目的では、スタックベースの実装は明らかに適用できません (スタックベースの構造と競合するため)。したがって、この場合、上位スコープのクロージャー データは、ガベージ コレクター (GC と呼ばれるガベージ コレクター) と参照カウント (参照カウント) の使用と組み合わせて、動的にメモリを割り当てること (「ヒープ」実装に基づいて) によって実装されます。 )。この実装はスタックベースの実装よりもパフォーマンスが劣りますが、どの実装でも常に最適化できます。関数が自由変数、関数パラメータ、または関数値を使用しているかどうかを分析し、状況に基づいて決定できます - はい データをスタックまたはヒープ内にあります。
理論的な部分を説明した後、ECMAScript でクロージャがどのように実装されるかを紹介します。ここでもう一度強調しておく価値があります。ECMAScript は静的 (字句) スコープのみを使用します (一方、Perl などの言語は変数宣言に静的スコープと動的スコープの両方を使用できます)。
関数 () {
}); foo(); // 20
alert(foo.__parent__.y) // 20
foo.__parent__.y = 30;
foo(); 🎜>// スコープチェーンを介して先頭に移動できます
alert(foo.__parent__.__parent__ === global) // true
alert(foo.__parent__.__parent__.x);
すべてのオブジェクトは [[Scope]] を参照します
ここにも注意してください: ECMAScript では、同じ親コンテキストで作成されたクロージャーは [[Scope]] 属性を共有します。言い換えれば、[[Scope]] の変数に対して特定のクロージャによって行われた変更は、他のクロージャによるその変数の読み取りに影響します:
つまり、すべての内部関数は同じ親スコープ
var x = 1;
firstClosure = function () { return x; }; x; };
x = 2; // 2 つのクロージャによって共有される [[Scope]] 内の AO["x"] に影響します。 、最初のクロージャの [[Scope]]
}
foo();
alert(firstClosure()) // 4
alter(secondClosure()); // 3
この関数に関して非常によくある誤解がありますが、ループ ステートメント内で関数を作成すると (内部的にカウントされる) 結果が得られないことがよくありますが、各関数には次のような結果が期待されます。独自の価値。
コードをコピーします
コードは次のとおりです。
var data = []; for (var k = 0; k data[k] = function () { alert(k)>} data[0](); // 0 ではなく 3 data[1]() // 1 ではなく 3
data[2](); // 2 ではなく 3 >
上記の例は、同じコンテキストで作成されたクロージャが [[Scope]] 属性を共有することを証明しています。したがって、上位コンテキストの変数「k」は簡単に変更できます。
コードをコピー
コードは次のとおりです:
activeContext.Scope = [
。 .. // その他の変数オブジェクト
{data: [...], k: 3} // アクティブなオブジェクト
]
コードをコピー
コードは次のとおりです:
var data = [];
for (var k = 0; k data[k] = (function _helper(x) {
return function () {
data[2]();上記のコードで何が起こるかを見てみましょう。関数「_helper」を作成した後、パラメーター「k」を渡すことで関数がアクティブになります。その戻り値も関数であり、対応する配列要素に格納されます。この手法により次のような効果が得られます。関数がアクティブ化されるたびに、「_helper」はパラメータ「x」を含む新しい変数オブジェクトを作成します。「x」の値は渡された「k」の値になります。このようにして、返される関数の [[スコープ]] は次のようになります:
コードをコピー
コードは次のとおりです。
data[0].[[Scope]] === [
... // その他の変数オブジェクト
親コンテキスト内のアクティブなオブジェクト AO: {data: [. ..], k: 3},
_helper のコンテキスト内のアクティブ オブジェクト AO: {x: 0}
];
data[1].[[Scope]] === [
... // 他の変数オブジェクト
親コンテキストのアクティブ オブジェクト AO: {data: [...], k: 3},
];
現時点では、関数の [[Scope]] 属性に実際に必要な値があることがわかります。この目的を達成するには、[[Scope]] に追加の変数オブジェクトを作成する必要があります。返された関数で「k」の値を取得したい場合、値は 3 のままであることに注意してください。
ところで、JavaScript を紹介する記事の多くは、追加で作成した関数だけがクロージャであると信じていますが、これは間違いです。実際には、この方法が最も効果的ですが、理論的な観点から見ると、ECMAScript の関数はすべてクロージャです。
ただし、上記の方法だけではありません。 「k」の正しい値は、次のような他の方法でも取得できます。
var data = [];
for (var k = 0; k (data[k] = function () {
alert (arguments.callee.x);
}).x = k; // k を関数の属性として使用します
}
// 結果も正しいです
data[0] (); // 0
data[1](); // 1
data[2](); // 2
別の機能がクロージャから返されます。 ECMAScript では、クロージャ内の return ステートメントは、制御フローを呼び出しコンテキスト (呼び出し元) に返します。 Ruby などの他の言語では、多くの形式のクロージャがあり、対応するクロージャの戻り値も異なります。次の方法が可能です。呼び出し元に直接返されることもあれば、場合によってはコンテキストから直接終了することもあります。 。
ECMAScript の標準終了動作は次のとおりです:
function getElement() {
[1, 2, 3].forEach(function (要素) {
if (要素 % 2 == 0) {
// getElement に戻る代わりに関数 "forEach" に戻ります function
alert('found: ' element) // found: 2
return element; >
} );
return
}
ただし、ECMAScript の try catch によって次の効果を実現できます。 🎜>
コードをコピーします
[1, 2, 3].forEach(function (要素) {
if (要素 % 2 == 0) {
// // getElement からの戻り値 "
alert('found: ' element); // 見つかった: 2
$break.data = element;
throw $break;
}
});
} catch (e) {
if (e == $break) {
return $break.data;
}
}
return null;
}
alert(getElement()); // 2
理論的バージョン
ここで、開発者がクロージャを次のように誤って理解していることを説明しましょう。親コンテキストから簡略化された内部関数を返すと、匿名関数のみがクロージャになり得ることも理解されます。
繰り返しますが、スコープ チェーンのため、すべての関数はクロージャです (関数の種類に関係なく、匿名関数、FE、NFE、FD はすべてクロージャです)。
関数の [[スコープ]] にはグローバル オブジェクトのみが含まれるため、関数コンストラクターを通じて作成された関数を除き、関数のタイプは 1 つだけです。
この問題をより明確にするために、ECMAScript のクロージャの定義の 2 つの正しいバージョンを示します。
ECMAScript では、クロージャは以下を指します。
理論的観点から: すべての機能。それらはすべて、作成時に上位コンテキストのデータを保存するためです。これは、関数内のグローバル変数へのアクセスは自由変数へのアクセスと同等であるため、単純なグローバル変数にも当てはまります。このとき、最も外側のスコープが使用されます。
実用的な観点から: 次の関数はクロージャとみなされます:
それが作成されたコンテキストが破棄された場合でもまだ存在します (たとえば、内部関数が親関数から返されます)
無料変数はコード内で参照されます
クロージャの実践的な使用法
実際に使用すると、クロージャは非常にエレガントなデザインを作成でき、funarg で定義されたさまざまな計算メソッドをカスタマイズできます。以下は、並べ替え条件関数をパラメータとして受け取る配列並べ替えの例です。
コードをコピー
コードは次のとおりです。 :
コピー コード
コードは次のとおりです:
[1, 2, 3].map(function (element) {
return element * 2;