JavaScriptでは、関数を他の関数内にネストできます。
クロージャーとは、親関数が実行された後でも、内部関数が親関数の変数にアクセスできる場合です。
ご覧のとおり、親関数の呼び出しで内部関数が引き続き存在する場合、これは興味深いものになります。 これは、次の状況で発生します。
- 内部関数は、タイマー、イベント、AJAXなどの非同期タスクの呼び出しとして使用されます。
- 親関数は、内部関数または内部関数を保存するオブジェクトを返します。
終了とタイマー
次の例では、 autorun()関数が実行された直後にローカル変数xが破棄されることを想定していますが、10分間は有効です。 これは、変数xが内部関数log()によって使用されるためです。 log()関数はクロージャです。
(function autorun(){ var x = 1; setTimeout(function log(){ console.log(x); }, 6000); })();
setInterval()を使用すると、クロージャー関数によって参照される変数は 、 clearInterval()が呼び出された後にのみ破棄されます。
閉会とイベント
外部関数からの変数がイベントハンドラーで使用されるたびに、クロージャーを作成します。 次の例では、 increment()イベントハンドラはクロージャです。
(function initEvents(){ var state = 0; $("#add").on("click", function increment(){ state += 1; console.log(state); }); })();
終了タスクと非同期タスク
外部関数からの変数が非同期呼び出しで使用されると、呼び出しはクロージャーになり、変数は非同期タスクが完了するまでアクティブのままになります。
タイマー、イベント、およびAJAX呼び出しはおそらく最も一般的な非同期タスクですが、他の例もあります:HTML5 Geolocation API、WebSockets API、およびrequestAnimationFrame()。
次のAJAXの例では、 updateList()の呼び出しはクロージャーです。
(function init(){ var list; $.ajax({ url: "https://jsonplaceholder.typicode.com/users"}) .done(function updateList(data){ list = data; console.log(list); }) })();
短絡とカプセル化
クロージャを確認する別の方法は、プライベートステート関数を使用することです。 短絡は状態をカプセル化します。
たとえば、プライベート状態でcount()関数を作成しましょう。 呼び出されるたびに、以前の状態を記憶し、次の連続番号を返します。 状態変数はプライベートであり、外部からはアクセスできません。
function createCount(){ var state = 0; return function count(){ state += 1; return state; } } var count = createCount(); console.log(count()); //1 console.log(count()); //2
同じプライベート状態を共有する多くのクロージャーを作成できます。 次の例では、 increment()とdecrement()は、同じプライベート状態変数を共有する2つのクロージャーです。 したがって、プライベート状態のオブジェクトを作成できます。
function Counter(){ var state = 0; function increment(){ state += 1; return state; } function decrement(){ state -= 1; return state; } return { increment, decrement } } var counter = Counter(); console.log(counter.increment());//1 console.log(counter.decrement());//0
純粋な機能に対する短絡
クロージングは、外部スコープの変数によって使用されます。
純粋な関数は、外部スコープの変数を使用しません。 純粋な関数は、渡された値のみを使用して計算された値を返す必要があり、副作用はありません。
非同期タスク:クロージャーとループ
次の例では、すべて同じ変数iを使用して、5つの非同期タスクに対して5つのクロージャーを作成します。 変数iはループ中に変化するため、すべてのconsole.log()は同じ値(最後)を表示します。
(function run(){ var i=0; for(i=0; i< 5; i++){ setTimeout(function logValue(){ console.log(i); //5 }, 100); } })();
この問題を解決する1つの方法は、IIFE(即時呼び出し関数式)を使用することです。 次の例では、さらに5つのクロージャーがありますが、5つ以上の異なる変数iがあります。
(function run(){ var i=0; for(i=0; i<5; i++){ (function autorunInANewContext(i){ setTimeout(function logValue(){ console.log(i); //0 1 2 3 4 }, 100); })(i); } })();
別のオプションは、ECMAScript 6の一部として使用できるletを介して変数を宣言する新しい方法を使用することです。これにより、各反復でブロックのスコープに対してローカルに変数を作成できます。
(function run(){ for(let i=0; i<5; i++){ setTimeout(function logValue(){ console.log(i); //0 1 2 3 4 }, 100); } })();
読みやすさの観点から、これがこの問題の最良の選択肢だと思います。
短絡およびガベージコレクター
JavaScriptでは、関数へのリンクがない場合、関数が戻った後にローカル関数変数は破棄されます。 回路自体が削除された後、回路のプライベート状態はガベージコレクションに適した状態になります。 これを可能にするために、クロージャーはそれへの参照をもはや持つべきではありません。
次の例では、最初にクロージャーadd()を作成します
function createAddClosure(){ var arr = []; return function add(obj){ arr.push(obj); } } var add = createAddClosure();
次に、2つの関数を定義します。1つは多数のaddALotOfObjects()オブジェクトを追加し、もう1つはclearAllObjects()を使用してnull参照を指定します 。 次に、両方の関数がイベントハンドラーとして使用されます。
function addALotOfObjects(){ for(let i=0; i<50000;i++) { add({fname : i, lname : i}); } } function clearAllObjects(){ if(add){ add = null; } } $("#add").click(addALotOfObjects); $("#clear").click(clearAllObjects);
[ 追加 ] をクリックすると、50,000個のアイテムがプライベートロック状態に追加されます。
「追加」を 3回クリックし、 「クリア」をクリックしてヌルリンクを設定しました。 その後、プライベート状態がクリアされます。
おわりに
閉鎖は、カプセル化ツールボックスで最適なツールです。 また、非同期タスクの呼び出しでの作業を簡素化します。 必要な変数を使用するだけで、呼び出し時に有効になります。
一方、クロージャがどのように機能するかを理解しておくと、不要になったときにガベージコレクション中にクロージャが確実に削除されます。
これは間違いなくプログラミング言語に入れられた最高の機能です。
閉鎖に関するダグラス・クロックフォード。