闭包与作用域链
1. 作用域
作用域 是指变量和函数的可访问范围。JavaScript 中主要有三种作用域:
- 全局作用域:在该作用域声明的变量在代码任何地方都能被访问。
- 函数作用域:在函数内部声明的变量只能在函数内部访问。
- 块级作用域:ES6 引入了
let和const,使{}包裹的代码块也形成作用域。
2. 作用域链
当查找变量时,JavaScript 引擎会沿着作用域链向上查找,直到找到变量或到达顶层作用域(全局作用域)。如果在全局作用域中仍未找到,则抛出 ReferenceError。
作用域链是在函数定义时确定的,而不是在函数调用时。这就是所谓的词法作用域。
3. 闭包
闭包 是指一个函数能够访问其词法作用域中的变量,即使该函数在其词法作用域之外被调用。
简单来说:闭包 = 函数 + 该函数能访问的自由变量。
核心特性
- 函数嵌套:必须有函数嵌套函数。
- 内部函数引用外部变量:内部函数引用了外部函数的变量。
- 外部函数返回内部函数:外部函数执行完毕后,其执行环境虽然销毁了,但其变量对象因为被内部函数引用而无法释放,这就形成了闭包。
示例
在这个例子中:
createCounter执行完毕后,count变量本应被回收。- 但因为返回的匿名函数引用了
count,形成了闭包。 count变量一直保存在内存中,直到counter被销毁。
4. 闭包的应用场景
(1) 数据私有化 / 模拟私有变量
利用闭包,我们可以隐藏变量,只通过特定的 API 暴露操作接口,防止全局污染和外部直接修改。
(2) 柯里化
将多参数函数转换为一系列单参数函数的过程。
(3) 防抖和节流
这是前端最常见的闭包应用。
5. 闭包与内存泄漏
闭包的主要缺点是内存占用。由于闭包会引用外部函数的变量,导致这些变量无法被垃圾回收机制回收。
如何避免内存泄漏?
- 尽量避免不必要的闭包。
- 在不再需要闭包时,手动解除引用(例如将变量赋值为
null)。
6. 面试常见问题
Q: 循环中的闭包陷阱?
原因:var 只有函数作用域,循环结束时 i 已经是 5。回调函数引用的是同一个 i。
解决:
- 使用
let(推荐):let有块级作用域,每次循环都会创建一个新的绑定。 - IIFE (立即执行函数):构造一个新的函数作用域来捕获当前的
i。

