概念
闭包(Closure):一个函数,加上它被定义时所处的环境(作用域中的变量)。
换句话说:函数可以"记住"并访问它被创建时的作用域中的变量,即使在那个作用域已经执行完毕之后。
例子(JavaScript)
function makeCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = makeCounter();
counter(); // 1
counter(); // 2
counter(); // 3关键:makeCounter() 执行完毕后,局部变量 count 通常应该被销毁。但因为返回的内部函数引用了它,所以 count 依然存在——每次调用 counter() 都会修改同一个 count。
这就是闭包。
为什么叫"闭包"
"闭包"指的是:函数把(close over)它需要的自由变量捕获(capture)进自己的作用域,形成一个封闭的实体。
用途
一、数据封装(模拟私有变量)
function createBankAccount(initial) {
let balance = initial; // 外部无法直接访问
return {
deposit: (amount) => { balance += amount; },
getBalance: () => balance
};
}balance 只能通过返回的方法访问——实现了某种程度的"私有性"。
注意:这是封装,不是安全边界(在浏览器控制台中仍可能通过调试手段访问)。
二、函数工厂
function multiplier(factor) {
return (x) => x * factor;
}
const double = multiplier(2);
const triple = multiplier(3);double 和 triple 是"记住"了不同 factor 的函数。
三、回调函数与事件处理
闭包让回调函数能"携带"上下文:
button.addEventListener('click', () => {
// 这里可以访问定义时的变量
console.log('User ' + userName + ' clicked');
});四、延迟执行(部分求值、柯里化)
function add(a) {
return (b) => a + b;
}
const addFive = add(5);
addFive(3); // 8五、模块模式
在 ES Module 普及之前,JavaScript 常用 IIFE(立即执行函数表达式)+ 闭包来实现模块:
const MyModule = (function() {
let privateVar = 0;
function privateFn() { ... }
return { publicFn: ... };
})();注意事项
一、内存占用
闭包会让被引用的变量无法被垃圾回收——只要闭包还活着,它捕获的环境就一直存在。
如果闭包捕获了很大的对象,且闭包生命周期很长,可能造成内存占用。
二、循环中的经典陷阱
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出:3, 3, 3(而不是 0, 1, 2)原因:var 是函数作用域,三个闭包共享同一个 i。循环结束时 i 已经是 3。
解决:
- 用
let(块级作用域,每次循环创建新的绑定); - 或用 IIFE 创建新的作用域。
三、Python 中的闭包
Python 支持闭包,但有一个常见陷阱:
funcs = []
for i in range(3):
funcs.append(lambda: i)
[f() for f in funcs] # [2, 2, 2]原因:Python 的闭包是晚绑定(late binding)——函数在调用时才查找变量的值,此时循环已结束。
解决:用默认参数捕获当前值:
funcs.append(lambda i=i: i)一句话
闭包的本质是函数 + 其定义环境。它让函数拥有了"状态"——这是函数式编程中非常重要的能力。
说明:本文为编程基础的知识整理。