一、闭包的核心定义(一句话概括)
闭包是函数嵌套中,内部函数引用了外部函数的变量/参数,且内部函数被外部访问时,形成的一种能保留外部函数作用域的特殊结构。
简单说:“内层函数记住了外层函数的变量,即使外层函数已经执行完毕”。
二、用代码示例直观理解(高频考点)
function outer() {
let count = 0; // 外部函数的局部变量
// 内部函数,引用了outer的变量count
function inner() {
count++;
console.log(count);
}
return inner; // 内部函数被返回(外部可访问)
}
// 执行外部函数,得到内部函数
const fn = outer();
// 调用内部函数(此时outer已执行完毕,但count仍被保留)
fn(); // 1
fn(); // 2
fn(); // 3
关键逻辑:
outer执行后,本应销毁的count变量,因被inner引用且inner被外部fn持有,所以count被“闭包”保留下来。
三、闭包的本质(底层原理)
- JavaScript中,函数执行时会创建作用域链:内部函数可以访问外部函数的变量,但外部不能访问内部。
- 当内部函数被外部引用时,外部函数的作用域不会被垃圾回收机制销毁,形成闭包。
- 闭包的核心是**“作用域的保留”**,而非“函数本身”。
四、闭包的主要作用(必答)
- 保存变量状态:让函数外部能间接访问函数内部的局部变量,且变量不会被销毁(如示例中的
count累加)。 - 模块化封装:隐藏内部实现,只暴露需要的接口,避免全局变量污染。
// 模块化示例:用闭包封装私有变量 function createCounter() { let count = 0; // 私有变量,外部无法直接修改 return { add: () => count++, get: () => count }; } const counter = createCounter(); counter.add(); console.log(counter.get()); // 1(只能通过暴露的方法操作)
五、问题
1. 循环中的闭包陷阱
// 问题:以下代码点击每个按钮都会输出3,为什么?如何修复?
for (var i = 0; i < 3; i++) {
document.getElementById(`btn${i}`).onclick = function() {
console.log(i); // 始终输出3(i是全局变量,循环结束后为3)
};
}
修复方案:用闭包保存每次循环的i值
// 方法1:立即执行函数(IIFE)创建闭包
for (var i = 0; i < 3; i++) {
(function(j) { // j接收当前i的值
document.getElementById(`btn${i}`).onclick = function() {
console.log(j); // 0、1、2(正确输出)
};
})(i);
}
// 方法2:用let声明(块级作用域,本质也是闭包)
for (let i = 0; i < 3; i++) { // let使每次循环的i是独立变量
document.getElementById(`btn${i}`).onclick = function() {
console.log(i); // 0、1、2
};
}
2. 闭包的缺点与注意事项
- 内存泄漏风险:闭包会保留外部函数的作用域,若长期不释放(如全局变量引用闭包),会占用过多内存,导致性能问题。
- 解决办法:不再使用时,手动将引用闭包的变量设为
null(如fn = null),让垃圾回收机制回收。
六、总结
“闭包是函数嵌套时,内部函数引用外部变量且被外部访问,导致外部作用域被保留的结构。
- 作用是保存状态、模块化封装;
- 常见场景如计数器、防抖节流、模块化开发;
- 需注意内存泄漏问题,及时释放引用。