什么是闭包?一句话先讲清
闭包就是函数与其词法作用域的捆绑,即使外部函数已经执行完毕,内部函数依旧能访问外部函数的变量。

(图片来源网络,侵删)
闭包形成的三个必要条件
- 函数嵌套:外层函数包裹内层函数。
- 内部函数引用外部变量:内层函数必须用到外层作用域的变量。
- 外部函数被调用并返回内部函数:只有返回了内部函数,外部变量才会被“锁”在内存中。
闭包常见疑问:为什么变量不会被回收?
浏览器垃圾回收机制使用标记清除。只要内部函数仍被外部引用,外层作用域的变量就被标记为“活跃”,因此不会被回收。
闭包应用场景有哪些?
1. 数据私有化
利用闭包隐藏变量,实现类似面向对象的私有属性。
function createCounter() {
let count = 0;
return {
inc() { count++; },
dec() { count--; },
get() { return count; }
};
}
const c = createCounter();
c.inc();
console.log(c.get()); // 1
2. 函数工厂
动态生成带有预设参数的函数,减少重复代码。
function makeMultiplier(x) {
return function(y) {
return x * y;
};
}
const double = makeMultiplier(2);
console.log(double(5)); // 10
3. 防抖与节流
在事件高频触发时,通过闭包保存定时器引用,控制函数执行频率。
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
4. 缓存计算结果(记忆化)
用闭包保存已计算结果,避免重复运算。

(图片来源网络,侵删)
function memoize(fn) {
const cache = {};
return function(n) {
if (cache[n] !== undefined) return cache[n];
return cache[n] = fn(n);
};
}
闭包常见坑:循环事件绑定
经典错误:在循环里直接给 DOM 绑定事件,结果所有事件都拿到最后的索引值。
// 错误示例
for (var i = 0; i < 3; i++) {
document.getElementById('btn' + i).onclick = function() {
alert(i); // 全部输出 3
};
}
// 正确做法:用闭包保存每次的 i
for (var i = 0; i < 3; i++) {
(function(j) {
document.getElementById('btn' + j).onclick = function() {
alert(j); // 0,1,2
};
})(i);
}
如何检测内存泄漏?
在 Chrome DevTools 的 Performance 面板录制快照,查看Detached DOM tree与Closure引用链,若闭包长期持有大对象且未释放,即为泄漏。
最佳实践:让闭包更安全
- 及时解除引用:使用完闭包返回的函数后,将其设为 null。
- 避免在全局作用域创建闭包:把闭包封装在模块或立即执行函数内。
- 使用 WeakMap 管理私有数据:当对象被销毁时,WeakMap 的键值对自动释放。
面试高频追问:闭包与作用域链区别?
作用域链是静态的,在代码编译阶段就确定;闭包是动态的,在运行时通过函数返回并保持对外部变量的引用。
一句话回顾
掌握闭包,等于掌握 JavaScript 的内存、作用域、模块化三大核心,写出既优雅又高效的代码。

(图片来源网络,侵删)
评论列表