定义
闭包是函数与其词法作用域(lexical environment)的组合,使得函数能够访问并“记住”其定义时的作用域链,即使函数在其定义的作用域外执行。
个人理解
和js本身的词法作用域有很大关系,词法作用域决定了,js作用域的关系是由编写位置决定,而不是执行位置决定 所以放闭包的内层函数引用外层函数时,在另外的外层作用域中还是可以访问到,闭包起到了对作用域内变量的一个保存作用
形成条件
- 内外函数嵌套;
- 内层函数引用外层函数的变量
- 返回内层函数
function outFn() {
let innerA = 1
return function innerFn() {
console.log(innerA)
}
}
let a = outFn()
a() // 1
// 错误示例 不算闭包
function outFn() {
let innerA = 1
function innerFn() {
console.log(innerA)
}
}
// 只满足了1,2两点,并未返回内层函数
// 导致了外层函数执行完之后,其作用域销毁了,内部函数和变量都被回收了
用途:
- 读取函数内部变量: 函数外可以访问函数内变量;
- 变量始终保持在内存中: 避免变量在函数执行后被销毁,
核心作用
-
可用于数据封装(私有化变量)
- 实现模块化,隐藏内部变量,避免全局污染
function outFn() { let count = 0 return { getInner() { return count }, addInner() { count++ } } } const a = outFn() a.addInner() a.getInner() -
保持变量状态
- 在异步报错中保持变量状态
for(var i = 0; i < 3; i++) { setTimeout(() => {console.log(i)}, 100) } // 使用闭包保持变量状态 for(var i = 0; i < 3; i++) { (function (j) { setTimeout(() => {console.log(j)}, 100) })(i) } -
函数柯里化(Currying)
- 通过闭包分步传递参数
function fn1(a) { return function fn2(b) { return a * b } } const c = fn1(2) c(4)
优点
- 模块化: 通过闭包隐藏内部实现,暴露接口;
- 灵活的状态管理: 在异步编程中保留上下文状态
- 避免全局污染: 通过私有变量减少命名冲突
缺点
- 内存泄漏风险
function init() {
const largeData = new Array(1000000).fill("data");
document.getElementById("btn").onclick = () => {
console.log(largeData.length); // largeData 无法释放
};
}
最佳实践
- 避免滥用闭包: 仅在需要封装数据和保持状态的时候使用
- 及时释放引用: 手动解除闭包对 DOM 或 全局对象的引用
- 模块化工具: 使用 ES6 的class 或 模块(import/export)替代闭包封装。)
闭包和词法作用域的关系
-
词法作用域是闭包的基石:
- 闭包的“记忆”能力完全依赖于词法作用域的静态规则。若作用域是动态的,闭包将无法正确捕获变量。
-
闭包是词法作用域的动态表现:
- 它通过保留作用域链,打破了词法作用域默认的生命周期限制,使得外部变量可以在函数执行后继续存活。