JavaScriptを深く理解するシリーズ(16) Closures_javascriptスキル
はじめに
この章では、JavaScript でよく議論されるトピックであるクロージャを紹介します。実際、閉店についてはすでに誰もが話し合っています。それにもかかわらず、ここでは理論的な観点からクロージャについて説明し、ECMAScript のクロージャが実際に内部でどのように動作するかを見ていきます。
前の記事で述べたように、これらの記事は一連の記事であり、相互に関連しています。したがって、この記事で紹介する内容をより深く理解するには、まず第 14 章のスコープチェーンと第 12 章の変数オブジェクトを読むことをお勧めします。
英語の原文: http://dmitrysoshnikov.com/ecmascript/chapter-6-closures/
はじめに
ECMAScript クロージャーについて直接議論する前に、やはり関数型プログラミングについて学ぶ必要があります。いくつかの基本的な定義。
ご存知のとおり、関数型言語 (ECMAScript もこのスタイルをサポートしています) では、関数はデータです。たとえば、関数を変数に割り当てたり、パラメータとして他の関数に渡したり、関数から返したりすることができます。このような関数には特別な名前と構造があります。
定義
関数引数 (「Funarg」) — 値が関数である引数です。
関数引数 (「Funarg」) — 値が関数である引数です。
例:
function exampleFunc(funArg) ) {
funArg();
}
exampleFunc(function () {
alert('funArg');
});上記の例では、funarg の実パラメータは、実際には exampleFunc に渡される匿名関数です。
前述したように、関数はパラメータとしてだけでなく戻り値としても使用できます。このような関数を返す関数は、関数値を持つ関数または関数値関数と呼ばれます。
})()();
という形式の関数です。通常のデータ (例: パラメータが渡されるとき、関数パラメータを受け入れるとき、または関数値を返すとき) は、ファーストクラス関数 (一般的に言えば、ファーストクラス オブジェクト) と呼ばれます。 ECMAScript では、すべての関数はファーストクラスのオブジェクトです。
通常のデータとして存在できる関数 (たとえば、パラメーターが渡される場合、関数パラメーターを受け入れる場合、または関数値を返す場合) は、第一級関数 (一般的に言えば、第一級オブジェクト) と呼ばれます。
ECMAScript では、すべての関数はファーストクラスのオブジェクトです。
それ自体をパラメータとして受け入れる関数は、自動適用関数または自己適用関数と呼ばれます:
コードをコピー
return;
}
})(); 戻り値として selfこの機能を自動レプリケーション機能または自己レプリケーション機能と呼びます。通常、「自己複製」という用語は文献で使用されます:
コードをコピー
コードをコピー
function modes(mode) {
registerMode(mode); // モードを登録します
return modes; // 関数自体を返します。
}
// 使用法、モードチェーン呼び出し
modes('roster')('accounts')('groups')
// 多少似ています: jQueryObject.addClass ("a") .toggle().removClass("b")
しかし、コレクションを直接渡すのは比較的効果的で直感的です。
関数パラメータで定義された変数は、「funarg」がアクティブ化されているときにアクセスできます (コンテキスト データを格納する変数オブジェクトはコンテキストが入力されるたびに作成されるため):
function testFn(funArg) {
//funarg がアクティブ化されると、ローカル変数 localVar は次のようになります。
funArg(10); // 20
funArg(20) // 30
}
testFn(function (arg) {
var localVar = 10;
alert(arg localVar);
});
ただし、ECMAScript では関数を親関数にカプセル化でき、次の変数を使用できることがわかります。親関数のコンテキスト。この機能により、funarg の問題が発生する可能性があります。
Funarg 問題
スタック指向プログラミング言語では、関数がアクティブ化されるたびに、これらの変数と関数パラメーターがスタックにプッシュされます。
関数が戻ると、これらのパラメータはスタックから削除されます。このモデルでは、関数を関数値として (たとえば、親関数からの戻り値として) 使用することに重大な制限が設けられています。ほとんどの場合、関数に自由変数がある場合に問題が発生します。
自由変数とは、関数内で使用される変数を指しますが、関数パラメーターでも関数のローカル変数でもありません。
例:
function testFn() {
var localVar = 10; innerFn( innerParam) {
alert(innerParam localVar);
return innerFn;
var someFn = testFn(); ); // 30
上記の例では、innerFn 関数の場合、localVar は自由変数です。
スタック指向モデルを使用してローカル変数を保存するシステムの場合、これは、testFn 関数呼び出しが終了すると、そのローカル変数がスタックから削除されることを意味します。このように、外部から innerFn への関数呼び出しが行われた場合、エラーが発生します(localVar 変数が存在しなくなるため)。
また、上記の例では、スタック指向の実装モデルでは、戻り値として innerFn を返すことは単純に不可能です。これは testFn 関数のローカル変数でもあるため、testFn が戻ったときにも削除されます。
もう 1 つの問題は、システムが動的スコープを使用し、関数が関数パラメーターとして使用される場合です。
次の例 (疑似コード) を見てください:
コードをコピーします
var z = 20;
foo(); // 10 – 静的スコープを使用、20 – 動的スコープを使用
})();
// foo をパラメータとした場合も同様
(function (funArg) {
var z = 30;
funArg () ; // 10 – 静的スコープ、30 – 動的スコープ
})(foo);
動的スコープを使用すると、システムが管理されることがわかります。変数の動的なスタックを通じて。したがって、自由変数は、関数の作成時に保存された静的スコープ チェーンではなく、現在アクティブな動的チェーンでクエリされます。
これにより競合が発生します。たとえば、Z がまだ存在するとしても (スタックから変数を削除する前述の例とは対照的に)、Z のどの値がさまざまな関数呼び出し (どのコンテキスト、どのスコープからのクエリ) を引き受けるのかという疑問が残ります。
上記では、関数が戻り値として返されるかどうか (最初のタイプの問題) と、関数が関数パラメーターとして使用されるかどうか (2 番目のタイプの問題) に応じて、2 つのタイプの funarg 問題について説明しています。 。
上記の問題を解決するために、クロージャの概念が導入されます。
クロージャは、コードのブロックと、コードのブロックが作成されたコンテキスト内のデータの組み合わせです。
次の例 (疑似コード) を見てみましょう:
コードをコピーします
コードは次のとおりです:
lexicalEnvironment: {x: 20} // コンテキストのコンテキストを検索
};
上記の例では、「fooClosure」の部分が擬似コードです。同様に、ECMAScript では、「foo」関数には、関数のコンテキストを作成するスコープ チェーンという内部プロパティがすでにあります。
「語彙」は通常省略されます。上の例は、クロージャの作成時にコンテキスト データが保存されることを強調しています。次回関数が呼び出されるとき、自由変数は保存された (クロージャ) コンテキストで見つかり、上記のコードに示されているように、変数 "z" の値は常に 10 になります。
定義では「コード ブロック」というより広い用語を使用しますが、通常 (ECMAScript では) よく使用する関数を使用します。もちろん、クロージャのすべての実装がクロージャと関数を結びつけるわけではありません。たとえば、Ruby 言語では、クロージャはプロシージャ オブジェクト、ラムダ式、またはコード ブロックである可能性があります。
コンテキストが破棄された後にローカル変数を保存するという目的では、スタックベースの実装は明らかに適用できません (スタックベースの構造と競合するため)。したがって、この場合、上位スコープのクロージャー データは、ガベージ コレクター (GC と呼ばれるガベージ コレクター) と参照カウント (参照カウント) の使用と組み合わせて、動的にメモリを割り当てること (「ヒープ」実装に基づいて) によって実装されます。 )。この実装はスタックベースの実装よりもパフォーマンスが劣りますが、どの実装でも常に最適化できます。関数が自由変数、関数パラメータ、または関数値を使用しているかどうかを分析し、状況に基づいて決定できます - はい データをスタックまたはヒープ内にあります。
ECMAScript クロージャの実装
理論的な部分を説明した後、ECMAScript でクロージャがどのように実装されるかを紹介します。ここでもう一度強調しておく価値があります。ECMAScript は静的 (字句) スコープのみを使用します (一方、Perl などの言語は変数宣言に静的スコープと動的スコープの両方を使用できます)。
var x = 10; 🎜>function foo() {
alert(x);
}
(function (funArg) {
var x = 20;
//変数 "x "関数の作成時に保存される (字句) コンテキストに保存される静的
funArg() // 20 ではなく 10
技術的に言えば、関数を作成した親コンテキストのデータは、関数の内部プロパティ [[Scope]] に格納されます。 [[Scope]] が何なのかわからない場合は、まず第 14 章を読むことをお勧めします。この章では、[[Scope]] について詳しく説明しています。 [[Scope]] とスコープ チェーンの知識を完全に理解していれば、クロージャも完全に理解できるようになります。
関数作成アルゴリズムによると、ECMAScript ではすべての関数がクロージャであることがわかります。これは、関数が作成されたときに (例外を除いて) 上位コンテキストのスコープ チェーンを保存するためです (この関数が後でアクティブ化します - [[スコープ]] は関数の作成時に存在します):
コードをコピー
// foo はクロージャ
foo:
[[Call]]:
[[スコープ]]: [
global: {
x: 10
}
],
... // その他のプロパティ
};
前述したように、関数が使用されていない場合は、自由変数の場合、実装は副作用ドメイン チェーンに保存されない可能性があります。ただし、ECMA-262-3 仕様には何も記載されていません。したがって、通常、すべてのパラメータは作成フェーズ中に [[Scope]] 属性に保存されます。
一部の実装では、クロージャ スコープに直接アクセスできます。たとえば、Rhino には関数の [[Scope]] 属性に非標準の __parent__ 属性があり、これは第 12 章で紹介されました:
コピー コード
関数 () {
}); 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]] の変数に対して特定のクロージャによって行われた変更は、他のクロージャによるその変数の読み取りに影響します:
つまり、すべての内部関数は同じ親スコープ
firstClosure = function () { return x; }; x; };
x = 2; // 2 つのクロージャによって共有される [[Scope]] 内の AO["x"] に影響します。 、最初のクロージャの [[Scope]]
}
foo();
alert(firstClosure()) // 4
alter(secondClosure()); // 3
この関数に関して非常によくある誤解がありますが、ループ ステートメント内で関数を作成すると (内部的にカウントされる) 結果が得られないことがよくありますが、各関数には次のような結果が期待されます。独自の価値。
コードをコピーします
上記の例は、同じコンテキストで作成されたクロージャが [[Scope]] 属性を共有することを証明しています。したがって、上位コンテキストの変数「k」は簡単に変更できます。
コードをコピー
コードは次のとおりです:
activeContext.Scope = [
{data: [...], k: 3} // アクティブなオブジェクト
]
コードをコピー
コードは次のとおりです:
var data = [];
return function () {
コードをコピー
コードは次のとおりです。
data[0].[[Scope]] === [
... // その他の変数オブジェクト
親コンテキスト内のアクティブなオブジェクト AO: {data: [. ..], k: 3},
_helper のコンテキスト内のアクティブ オブジェクト AO: {x: 0}
];
... // 他の変数オブジェクト
親コンテキストのアクティブ オブジェクト 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 によって次の効果を実現できます。 🎜>
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;
関数パラメータを使用すると、検索メソッドを簡単に実装し、無制限の検索条件をサポートできます:
someCollection.find(function (element) {
return element.someProperty == 'searchCondition';
});各配列要素に関数を適用する一般的な forEach メソッドなどのアプリケーション関数も含まれます。
}
}); // 1, 3
ちなみに、関数オブジェクトの apply メソッドや call メソッドは、関数型プログラミングの応用関数としても使用できます。 apply と call は、「これ」について説明するときにすでに紹介されています。ここでは、それらをアプリケーション関数、つまり引数 (apply の引数リスト、call の個々の引数) に適用される関数として考えます。 🎜>
コールバックもあります Function
// この時点で変数 "x" の値はすでに存在します
alert(x) // 10
};
;また、カプセル化されたスコープを作成してヘルパー オブジェクトを非表示にすることもできます:
コードをコピーします
コードは次のとおりです:
object.getX = function _getX () {
return x;
})(foo);
alert(foo.getX()); // クロージャ「x」を取得します。
概要
この記事では、ECMAScript-262-3 に関する理論的な知識をさらに紹介します。これらの基本理論は、ECMAScript のクロージャーの概念を理解するのに役立つと思います。ご質問がございましたら、コメントにてお返事させていただきます。
その他の参考資料
JavaScript クロージャー (Richard Cornford 著)
フナルグ問題

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック









C++ では、クロージャは外部変数にアクセスできるラムダ式です。クロージャを作成するには、ラムダ式の外部変数をキャプチャします。クロージャには、再利用性、情報の隠蔽、評価の遅延などの利点があります。これらは、イベント ハンドラーなど、外部変数が破棄されてもクロージャが外部変数にアクセスできる現実の状況で役立ちます。

C++ ラムダ式は、関数スコープ変数を保存し、関数からアクセスできるようにするクロージャーをサポートしています。構文は [キャプチャリスト](パラメータ)->戻り値の型{関数本体} です。 Capture-list は、キャプチャする変数を定義します。[=] を使用してすべてのローカル変数を値によってキャプチャするか、[&] を使用してすべてのローカル変数を参照によってキャプチャするか、[variable1, variable2,...] を使用して特定の変数をキャプチャできます。ラムダ式はキャプチャされた変数にのみアクセスできますが、元の値を変更することはできません。

クロージャは、外部関数のスコープ内の変数にアクセスできる入れ子関数です。その利点には、データのカプセル化、状態の保持、および柔軟性が含まれます。デメリットとしては、メモリ消費量、パフォーマンスへの影響、デバッグの複雑さなどが挙げられます。さらに、クロージャは匿名関数を作成し、それをコールバックまたは引数として他の関数に渡すことができます。

タイトル: クロージャによって引き起こされるメモリ リークと解決策 はじめに: クロージャは JavaScript における非常に一般的な概念であり、内部関数が外部関数の変数にアクセスできるようにします。ただし、クロージャを誤って使用すると、メモリ リークが発生する可能性があります。この記事では、クロージャによって引き起こされるメモリ リークの問題を調査し、解決策と具体的なコード例を提供します。 1. クロージャによるメモリリーク クロージャの特徴は、内部関数が外部関数の変数にアクセスできることです。つまり、クロージャ内で参照される変数はガベージコレクションされません。不適切に使用すると、

関数ポインタとクロージャが Go のパフォーマンスに与える影響は次のとおりです。 関数ポインタ: 直接呼び出しよりわずかに遅くなりますが、可読性と再利用性が向上します。クロージャ: 一般に遅いですが、データと動作をカプセル化します。実際のケース: 関数ポインターは並べ替えアルゴリズムを最適化でき、クロージャーはイベント ハンドラーを作成できますが、パフォーマンスの低下をもたらします。

はい、コードの単純さと読みやすさは、連鎖呼び出しとクロージャーによって最適化できます。連鎖呼び出しは、関数呼び出しを流暢なインターフェイスにリンクします。クロージャは再利用可能なコード ブロックを作成し、関数の外の変数にアクセスします。

Java のクロージャを使用すると、外部関数が終了した場合でも、内部関数が外部スコープの変数にアクセスできるようになります。匿名の内部クラスを通じて実装されると、内部クラスは外部クラスへの参照を保持し、外部変数をアクティブに保ちます。クロージャによりコードの柔軟性が向上しますが、匿名の内部クラスによる外部変数への参照により、それらの変数が存続するため、メモリ リークのリスクに注意する必要があります。

クロージャでのメモリリークを防ぐにはどうすればよいでしょうか?クロージャは JavaScript の最も強力な機能の 1 つであり、関数のネストとデータのカプセル化を可能にします。ただし、クロージャでは、特に非同期やタイマーを扱う場合にメモリ リークが発生する傾向があります。この記事では、クロージャでのメモリ リークを防ぐ方法を説明し、具体的なコード例を示します。メモリ リークは通常、オブジェクトが不要になったにもかかわらず、そのオブジェクトが占有しているメモリを何らかの理由で解放できない場合に発生します。クロージャ内で、関数が外部変数を参照する場合、これらの変数は
