**闭包(Closure)**是函数式编程和许多现代语言(尤其是 JavaScript、Python、Swift、Kotlin 等)中的核心概念。
1. 概念是什么?
闭包是一个函数,加上它被创建时所能访问的**词法作用域(lexical scope)**中的变量的引用。
简单说:
- 一个内部函数可以访问外部函数的变量。
- 即使外部函数已经执行完毕、返回了,内部函数依然能“记住”并使用那些外部变量。
- 这些被“记住”的变量和内部函数一起,构成了闭包。
关键特征:
- 词法作用域:变量的作用域在代码书写时就确定了,而不是运行时。
- 持久化:外部函数的局部变量不会因为函数执行结束而被销毁(只要还有闭包引用它)。
2. 作用是什么?
闭包的主要作用可以概括为:
| 作用 | 说明 |
|---|---|
| 数据私有化 / 封装 | 让变量不被外部直接访问,只能通过特定接口操作 |
| 状态保持 | 函数可以“记住”上一次的状态,实现有状态的函数 |
| 延迟执行 / 回调 | 把函数和它的环境一起传递,稍后再执行时仍能访问原来的变量 |
| 工厂函数 / 函数工厂 | 通过参数生成具有不同行为的函数 |
| 模块化 | 在没有 class 的早期语言中模拟私有变量和模块 |
3. 能解决什么实际问题?
实现私有变量(真正的封装)
在 JavaScript 中没有真正的 private 关键字(ES2022 之前),闭包是实现私有数据的经典方式。函数记忆上一次的计算结果 / 状态
比如计数器、节流、防抖、缓存等。解决循环中的异步问题(经典坑)
在for循环里创建多个异步回调时,闭包可以正确捕获每次循环的值。高阶函数与柯里化
把多参数函数变成一系列单参数函数,或者实现部分应用(partial application)。事件处理与回调
点击按钮时需要访问创建时的上下文变量。模块模式(Module Pattern)
在早期 JS 中实现命名空间和私有成员。
4. 典型案例
案例 1:计数器(最经典)
functioncreateCounter(){letcount=0;// 私有变量returnfunction(){// 闭包count++;returncount;};}constcounter1=createCounter();constcounter2=createCounter();console.log(counter1());// 1console.log(counter1());// 2console.log(counter2());// 1 (各自独立)案例 2:函数工厂(根据参数生成不同行为)
functionmakeMultiplier(factor){returnfunction(x){returnx*factor;};}constdouble=makeMultiplier(2);consttriple=makeMultiplier(3);console.log(double(5));// 10console.log(triple(5));// 15案例 3:解决循环 + 异步的经典问题
// 错误写法(所有都输出 3)for(vari=0;i<3;i++){setTimeout(()=>console.log(i),100);}// 用闭包修复for(vari=0;i<3;i++){(function(j){// 立即执行函数创建闭包setTimeout(()=>console.log(j),100);})(i);}// 现代写法(let 本身就有块级作用域)for(leti=0;i<3;i++){setTimeout(()=>console.log(i),100);}案例 4:防抖(Debounce)
functiondebounce(fn,delay){lettimer=null;returnfunction(...args){clearTimeout(timer);timer=setTimeout(()=>{fn.apply(this,args);},delay);};}consthandleSearch=debounce((keyword)=>{console.log('搜索:',keyword);},300);案例 5:模块模式(IIFE + 闭包)
constCalculator=(function(){letresult=0;// 私有return{add(x){result+=x;returnthis;},subtract(x){result-=x;returnthis;},getResult(){returnresult;}};})();Calculator.add(10).subtract(3);console.log(Calculator.getResult());// 7总结
闭包 = 函数 + 它诞生时的环境。
它让函数拥有“记忆力”,能够封装数据、保持状态、延迟执行,是 JavaScript 等语言实现高级抽象的重要基础。理解闭包,也就理解了作用域、生命周期和函数式编程的核心思想。