理解闭包的定义与核心机制
掌握闭包如何实现私有变量和数据封装
了解闭包在异步编程、模块化、函数柯里化中的实际应用
识别闭包使用中的常见陷阱(如 this 绑定问题、内存泄漏)
能够在项目中合理利用闭包提升代码质量
闭包是指一个函数能够访问并“记住”其词法作用域,即使该函数在其原始作用域之外被调用。
通俗理解:闭包 = 函数 + 其创建时的环境(外层变量)
function outer() {
let outerVar = "I'm in the outer scope!";
function inner() {
console.log(outerVar);
outerVar = "Updated";
}
return inner; // 返回内部函数
}
const closure = outer(); // outer() 执行完毕,但 inner 仍能访问 outerVar
closure(); // 输出:I'm in the outer scope!
closure(); // 输出:Updated关键点:
inner函数形成了闭包即使
outer()已执行结束,inner仍能读写outerVar这是因为
inner捕获了outer的作用域链
词法作用域
JavaScript 使用词法作用域:
函数的作用域由定义位置决定,而非调用位置
内部函数可以访问外层函数的所有变量

因此,
inner在任何地方调用,都能“回溯”到outer的作用域。
数据封装
function counter() {
let count = 0; // 私有变量,外部无法直接访问
return function () {
count++;
return count;
};
}
const increment = counter();
console.log(increment()); // 1
console.log(increment()); // 2
console.log(increment()); // 3优势:count 被安全封装,只能通过返回的函数修改,避免全局污染或意外篡改。
利用立即执行函数表达式创建模块:
const counter = (function () {
let count = 0;
return {
increment: function () {
count++;
console.log(count);
},
reset: function () {
count = 0;
console.log("Counter reset");
},
};
})();
counter.increment(); // 1
counter.increment(); // 2
counter.reset(); // Counter reset这是早期 JavaScript 实现“类”和“私有成员”的常用模式。
如
setTimeout
function createTimers() {
for (let i = 1; i <= 3; i++) {
setTimeout(function () {
console.log(`Timer ${i}`);
}, i * 1000);
}
}
createTimers();
// 输出:
// Timer 1
// Timer 2
// Timer 3为什么能正确输出
1, 2, 3?
因为使用了
let,每次循环创建新的块级作用域每个
setTimeout回调形成闭包,捕获各自循环中的i
若改用 var,所有回调共享同一个 i,最终输出 Timer 4(三次)。
柯里化:将多参数函数转换为一系列单参数函数,依赖闭包保存已传入的参数。
function add(a) {
return function(b) {
return a + b; // 闭包记住 a
};
}
const addTwo = add(2);
console.log(addTwo(3)); // 5
console.log(addTwo(4)); // 6应用:配置预设参数、创建专用工具函数(如
fetchUserById = fetchUser.bind(null, 'user'))
this 陷阱闭包本身不绑定 this,而 this 由调用方式决定,容易导致意外行为:
function Person(name) {
this.name = name;
this.sayName = function () {
console.log(this.name); // 正常:this 指向实例
};
// 问题:普通函数中的 this 指向全局对象(非严格模式)或 undefined(严格模式)
setTimeout(function () {
console.log(this.name); // undefined!
}, 1000);
}
const p = new Person("Coder");
p.sayName(); // Coder
// 1秒后:undefined解决方案
使用 bind() 绑定 this:
setTimeout(function () {
console.log(this.name);
}.bind(this), 1000);使用箭头函数(继承外层 this):
setTimeout(() => {
console.log(this.name); // 正确!
}, 1000);闭包会阻止外层变量被垃圾回收,如果长期持有大对象引用,可能导致内存占用过高。
function problematicClosure() {
const largeData = new Array(1000000).fill('*');
return function() {
// 即使不使用 largeData,它也不会被释放!
console.log('Still alive');
};
}
const fn = problematicClosure();
// largeData 一直存在于内存中,直到 fn 被销毁建议:及时解除不必要的引用,或在不需要时将变量设为 null。
每个闭包都会保留其作用域链,大量闭包可能增加内存消耗。
权衡:闭包带来强大能力,但也需谨慎使用,避免过度嵌套或无意义的闭包。
以下代码输出什么?为什么?
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}如何修改使其输出 0, 1, 2?(提供至少两种方法)
为什么下面的代码中,secret 变量无法从外部访问?这体现了闭包的什么特性?
const bank = (function() {
let secret = '123456';
return { getSecret: () => secret };
})();