闭包是函数与其词法作用域的组合,使函数能访问并记住创建时环境中的自由变量;即使外层函数执行完毕,只要内层函数仍存在,引擎就会保留其作用域。

闭包是 JavaScript 中一个核心但容易被误解的概念。它不是某种语法结构,而是函数与其词法作用域的组合——简单说,就是一个函数记住了它被创建时所处的环境,并能在之后继续访问这个环境里的变量。
闭包的本质:函数 + 自由变量的环境
所谓“自由变量”,是指函数内部没有定义、也没有作为参数传入,但能在函数中访问的变量。这些变量来自外层函数(或全局)作用域。当内层函数在外部被调用时,如果还能访问这些变量,就形成了闭包。
关键点在于:即使外层函数已经执行完毕、本该销毁其执行上下文,只要内层函数还存在(比如被返回、被保存),JavaScript 引擎就会保留那个外层作用域,供闭包使用。
示例:
立即学习“Java免费学习笔记(深入)”;
function createCounter() {let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
这里 createCounter 执行后本该释放 count,但返回的匿名函数仍能读写它——这就是闭包在起作用。
封装私有状态:避免全局污染和意外修改
闭包天然支持数据隐藏。通过外层函数定义变量,只暴露有限接口(如返回的函数),可防止外部直接访问或篡改内部状态。
- 适合实现计数器、缓存容器、配置管理器等需要“内部记忆”的模块
- 比用 class + #私有字段(ES2022)更早可用,兼容性更好
- 避免把临时状态挂到全局对象或 DOM 元素上,减少命名冲突风险
事件处理与异步回调中的变量绑定
循环中为多个元素绑定事件时,若直接使用循环变量,常出现“所有回调都用最后一个值”的问题。闭包可解决:
for (let i = 0; i buttons[i].onclick = function() {console.log('Clicked button', i); // 正确输出 0,1,2...
};
}
上面用了 let(块级作用域),本质也是闭包机制在支撑。若用 var,则需手动构造闭包:
buttons[index].onclick = function() {
console.log('Clicked button', index);
};
})(i);
}
函数工厂与柯里化(Currying)
闭包让“生成函数”变得自然:根据初始参数预先设定部分行为,返回一个定制化的新函数。
- 日志函数:固定前缀,如
const logError = makeLogger('[ERROR]') - API 封装:预设 base URL 或 token,如
const fetchUser = createApiCaller('/api/users') - 防抖/节流函数:闭包保存定时器 ID 和上一次触发时间
实际项目中要注意的坑
闭包很强大,但也带来内存和性能考量:










