Skip to content
Latchkey

クロージャとは?スコープを覚えている関数

クロージャとは、定義されたときに捕捉した周囲の変数と対になった関数であり、そのスコープが終わった後でもそれらの変数を使い続けられます。

ある関数が別の関数の中で定義されると、その関数は外側の関数の変数を参照できます。クロージャとは、その内側の関数と、捕捉した変数への生きたリンクを合わせたものです。外側の関数が戻った後でも、クロージャはそれらを生かし続け、読み書きできるため、クロージャは強力であり、時に驚かせるものになります。

クロージャが捕捉するもの

クロージャは、生成されたスコープの変数への参照を捕捉するのであって、その瞬間の値だけを捕捉するのではありません。したがって、捕捉した変数が後で変わると、クロージャは新しい値を見ます。これがクロージャにプライベートな状態を維持させる仕組みです。

クロージャが役立つ理由

  • カウンターやcacheのような、関数の背後に隠されたプライベートな状態。
  • 特化した関数を生み出すファクトリ関数。
  • 生成された場所のコンテキストを覚えているcallback。
  • 一部の引数を事前に固定する部分適用。

古典的なループの落とし穴

よくあるバグは、ループ内で同じループ変数をすべて捕捉するクロージャを生成することです。言語または新しいバインディングが変数を反復ごとにスコープしない限り、各クロージャは自分の反復時の値ではなく最終的な値を見ます。

クロージャとメモリ

クロージャは捕捉した変数を生かし続けるため、意図せず大きなオブジェクトを保持し、ガベージコレクションを妨げ得ます。長寿命のリストやレジストリに保存されたクロージャは、よくある静かなメモリリークの原因です。

手短な例

ローカルの count をインクリメントする関数を返す makeCounter() はクロージャを生成します。返される各関数は、呼び出し間で持続する自分専用のプライベートな count を持ちます。

CIにおけるクロージャ

大きな捕捉オブジェクトを保持するクロージャは、長いテスト実行の間にメモリを増やし、小さなrunnerでOOM killを引き起こし得ます。より大きなrunner(Latchkey)はより多くの余裕を与え、一時的なOOM killを自動でリトライしますが、捕捉しすぎるクロージャは依然として修正に値します。

重要なポイント

  • クロージャは関数に、それが定義された場所の捕捉された変数を加えたものである。
  • 参照を捕捉するため、後の変更も見え、プライベートな状態を保持できる。
  • クロージャは大きなオブジェクトを保持し得て、長期保存するとメモリをリークし得る。

関連ガイド