掌握闭包如何捕获变量,从而封装状态并避开作用域陷阱。
在 Kodokon 中打开本课闭包并不是什么高深莫测的技巧:它是一种机制,让函数得以持续访问其定义所在的词法作用域,而不是被调用所在的作用域。引擎不会复制值:它保留着对该词法环境的活引用。因此,对同一个工厂函数的两次调用会产生两个相互独立的环境,这使得闭包成为该语言中最轻量的封装工具。
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
}从模式的角度看,闭包是记忆化(memoization)和模块模式的基础。与类相比,闭包无需 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 = ...) 循环中,在循环体内部创建的三个 setTimeout 回调会打印出什么?createCounter 能保证 count 始终保持私有?