🔗
クロージャとスコープチェーン — 関数が外部変数を記憶する原理
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変数実装(モジュールパターン)
カリー化/部分適用 — 引数を事前固定した関数生成