805 字
4 分钟
JavaScript 闭包
.png)
核心定义
闭包是函数与其词法作用域的组合:
- 函数可以记住并访问其声明时的作用域链
- 即使函数在原始作用域外执行
- 闭包保留对外部变量的引用(非拷贝) 这种机制允许数据封装、状态保持和模块化编程,但也可能导致内存泄漏需谨慎使用。
工作原理
- 函数被创建时,保存其当前词法作用域链
- 当函数执行时,即使其声明作用域已销毁
- 仍可通过内部作用域链访问外部变量
- 外部变量不会被垃圾回收(因被引用)
- 每次外部函数调用都会创建新闭包
flowchart TD A[外部函数执行] --> B[创建内部函数] A --> C[声明变量 x] B --> D[返回内部函数] A -->|结束| E[外部作用域销毁] D --> F[调用内部函数] F --> G{访问 x?} G -->|通过闭包| H[成功读取 x]
关键点
- 词法作用域:闭包基于函数声明位置(非调用位置)
- 变量引用:捕获的是变量本身(非值快照)
- 内存保留:外部变量不会被 GC 回收
- 每次调用新闭包:外部函数每次调用创建独立闭包
- 封装性:实现私有变量(如模块模式)
- 性能影响:过度使用可能导致内存泄漏
常见误区
- 循环闭包陷阱:在循环中创建闭包捕获索引变量(输出意外值)
- 内存泄漏:DOM 元素引用未释放(如事件监听器)
- 误认值拷贝:认为闭包保存变量初始值(实际是引用)
- 过度使用:所有函数都误认为闭包
this
绑定混淆:闭包内this
不继承外部函数(需箭头函数或bind
)
应用场景
场景 | 示例 | 闭包作用 |
---|---|---|
数据封装 | 模块模式(立即执行函数) | 创建私有变量和公共 API |
事件处理 | button.addEventListener('click', (function(){...})()) | 保留事件回调状态 |
函数工厂 | function multiplier(factor) { return x => x * factor } | 生成定制函数 |
防抖/节流 | debounce(func, delay) | 保持计时器 ID 状态 |
柯里化函数 | const add = a => b => a + b | 分步传递参数 |
异步回调 | setTimeout(() => console.log(x), 100) | 保留回调所需变量 |
关联知识
- 作用域链:闭包实现的基础(函数保留声明时作用域)
- 执行上下文:包含变量对象(VO)和作用域链
- 垃圾回收:
- 引用计数(循环引用问题)
- 标记清除(现代浏览器使用)
- 内存泄漏:
- 未移除的事件监听器
- 意外的全局变量
- 脱离 DOM 的引用
- 设计模式:
- 模块模式
- 工厂函数
- 单例模式
this
机制:闭包内this
默认指向全局对象(严格模式undefined
)- 箭头函数:无自身
this
,继承外部作用域的this
💡 黄金法则:
- 闭包 = 函数 + 声明时的作用域链
- 循环内闭包用
let
或立即执行函数(IIFE)解决索引问题 - 移除事件监听和取消定时器防止内存泄漏 使用 Chrome DevTools 的 Memory 面板检测闭包内存占用!