クロージャが変数をどのように捕捉するかを習得し、状態をカプセル化してスコープの落とし穴を避けられるようになりましょう。
このレッスンを Kodokon で開くクロージャは何か特殊な裏技ではありません。それは関数が、呼び出された場所ではなく 定義された レキシカルスコープへのアクセスを保ち続ける仕組みです。エンジンは値をコピーするのではなく、レキシカル環境への 生きた参照 を保持します。したがって同じファクトリを2回呼び出すと、互いに独立した2つの環境が生成され、これがクロージャをこの言語で最も軽量なカプセル化ツールにしています。
function createCounter() {
let count = 0;
return {
increment: () => ++count,
reset: () => { count = 0; },
};
}
const counterA = createCounter();
const counterB = createCounter();
counterA.increment();
counterA.increment();
console.log(counterA.increment()); // 3
console.log(counterB.increment()); // 1実務でよくある古典的な罠: var はブロックではなく、それを囲む関数に束縛されます。ループの内側では、各クロージャは 同じ 変数を捕捉することになります。let を使うと、各反復ごとに新しいレキシカル環境が作られるため、各捕捉が別々になります。さらに 一時的デッドゾーン(TDZ)もあります。let や const の変数はブロックに入った瞬間から存在しますが、宣言より前に読み取ろうとすると ReferenceError を投げます。
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // 3, 3, 3
}
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 0); // 0, 1, 2
}パターンの観点では、クロージャは メモ化 とモジュールパターンの基礎です。クラスと比べると、クロージャは this やプロトタイプなしで本物のプライバシーを提供します。その代わり、数千のインスタンスを作る場合はメモリ使用量がやや多くなります。各インスタンスが自前の関数を持つためです。
function memoize(fn) {
const cache = new Map();
return function (key) {
if (cache.has(key)) return cache.get(key);
const result = fn(key);
cache.set(key, result);
return result;
};
}
const square = (n) => n * n;
const fastSquare = memoize(square);
console.log(fastSquare(9)); // computed
console.log(fastSquare(9)); // read from the cachefor (var i = ...) ループの中で、ループ本体の内側で作られた3つの setTimeout コールバックは何をログ出力しますか?createCounter は count がプライベートに保たれることを保証するのですか?