🔗

クロージャとスコープチェーン — 関数が外部変数を記憶する原理

Lexical Environmentオブジェクトが作るスコープチェーンの内部構造

function makeCounter() {
  let count = 0;
  return function() { return ++count; };
}
const counter = makeCounter();
counter(); // 1
counter(); // 2 — countが生きている

makeCounter()は既にリターンしたのに内部関数がcountを使い続けられる。これがクロージャ。

V8内部 — Lexical Environment

関数実行ごとにLexical Environmentオブジェクトが生成。ローカル変数と外部環境(outer)参照を保存。

匿名関数の[[Environment]]内部スロットがmakeCounterのLexical Environmentを参照。この参照が生きている限りcountはGCされない。

counter()呼び出し:自分のLexical Environmentでcountを探す→ない→outerを辿る→makeCounterの環境でcountを見つける。これがスコープチェーン

メモリリーク注意

クロージャが外部変数を参照するとその環境がGCされない。イベントリスナーのクロージャを解除しないとメモリリーク。

キーポイント

1

関数生成時[[Environment]]スロットに現在のLexical Environmentが保存される

2

変数参照時に自分の環境→outer→outer...の順でスコープチェーンを辿る

3

外部変数を参照する関数が生きていればそのLexical EnvironmentはGCされない

4

イベントリスナーのクロージャは必ずremoveEventListenerで解除

ユースケース

データ隠蔽 — クロージャでprivate変数実装(モジュールパターン) カリー化/部分適用 — 引数を事前固定した関数生成

参考資料