闭包是函数记住并访问其定义时词法环境的能力;for循环中事件绑定总输出最后一个i,因var声明导致i被共享,未主动创建独立闭包。
闭包不是语法糖,也不是高级技巧——它是 JavaScript 作用域链自然运作的结果:一个函数记住了它定义时的词法环境,并能在之后持续访问其中的变量,哪怕外部函数早已执行完毕。
for 循环里绑事件总弹出最后一个 i?这是闭包最常被问到的“翻车现场”。根本原因不是闭包错了,而是你没主动创建它。
错误写法(var + 普通回调):
const buttons = document.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function() {
alert(i); // 总是弹 3(假设共 3 个按钮)
});
}问题在哪?
var 声明的 i 是函数作用域,整个循环共用一个 i
i 已变成 3
正确做法(用闭包“封存”每次的 i):
let 替代 var(块级作用域自动解决,本质是引擎为每次迭代生成独立绑定)for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', (function(index) {
return function() { alert(index + 1); };
})(i));
}createCounter() 为什么能记住上一次的 count?这不是魔法,是闭包在“扛住”垃圾回收:外部函数的局部变量只要被内部函数引用,就不会被释放。
典型结构:
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2关键点:
createCounter() 执行完后,其执行上下文本该销毁,但 count 被返回的匿名函数持续引用count 的引用,也携带了定义它的词法环境counter(),都是在操作同一个 count 实例,不是重新初始化⚠️ 注意:如果忘了把闭包函数赋值给变量(比如直接写 createCounter()();),那闭包一用即弃,count 无法复用。
JavaScript 没有 private 关键字,但闭包可以做到「外部不可直接访问,只能通过暴露的方法操作」。
实操模式(模块模式):
function createBankAccount(initialBalance) {
let balance = initialBalance; // 私有数据,外部碰不到
return {
deposit(amount) {
balance += amount;
return balance;
},
withdraw(amount) {
if (amount <= balance) {
balance -= amount;
return balance;
}
throw new Error('Insufficient funds');
},
getBalance() {
return balance; // 只读访问
}
};
}
const account = createBankAccount(100);
console.log(account.deposit(50)); // 150
console.log(account.balance); // undefined ← 真的私有
为什么安全?
balance 始终在 createBankAccount 的局部作用域里balance,但外部代码无任何路径直接读写它闭包强大,但滥用会带来真实代价。
function attachHandler(element) {
const handler = function() {
console.log(element.id); // 闭包引用了 element
};
element.addEventListener('click', handler);
// 忘了在 element 销毁时 removeEventListener → element 和 handler 都卡在内存里
}counte
r 共享一个 count)i, val),很难快速定位谁在持有什么真正难的不是写出闭包,而是判断:这个变量是否真的需要被长期持有?有没有更轻量的替代(比如用 dataset 存状态,或用现代 Hook 管理)?