闭包 (Closure) 笔记
1. 什么是闭包
闭包是 JavaScript 中的一种函数结构,它允许函数“记住”并访问定义在它外部的变量,即使外部函数已经执行结束。
换句话说,闭包是函数与其词法作用域的组合。
2. 闭包的形成条件
闭包通常由以下三个条件形成:
- 函数嵌套:内部函数定义在外部函数内部。
- 访问外部变量:内部函数使用了外部函数的变量。
- 外部函数返回内部函数或将其赋值给其他作用域:使内部函数在外部函数执行后仍然可访问外部变量。
1 | function outer() { |
解释:inner 能访问 outer 的 count,形成闭包。
3. 闭包的作用与用途
3.1 数据封装与私有化
闭包可以模拟“私有变量”,保护数据不被外部直接访问。
1 | function createCounter() { |
3.2 延迟执行 & 回调
闭包可以延迟访问变量的值,用于回调函数、事件处理等。
1 | function delayMessage(msg) { |
3.3 维持状态
闭包可以让函数记住状态信息,适合实现状态机、计数器等功能。
4. 闭包的注意事项
内存泄漏
- 闭包会保持对外部作用域变量的引用,导致这些变量不会被垃圾回收。
- 尤其在大量动态创建闭包的场景下,需要注意释放不必要的引用。
性能问题
- 不当使用闭包可能导致额外内存开销。
- 尽量避免在循环中直接创建大量闭包,或者使用
let循环变量+立即执行函数优化。
循环中闭包问题
- ES5
var声明变量时容易陷入经典闭包陷阱。
- ES5
1 | for (var i = 0; i < 3; i++) { |
5. 闭包原理(深入理解)
- 词法作用域
- JS 函数会根据定义的位置确定其作用域链。
- 函数引用外部变量
- 当闭包存在时,外部函数的作用域不会销毁,内部函数持续持有引用。
- 作用域链与执行上下文
- 内部函数访问变量时,会沿作用域链查找,直到找到变量或到达全局作用域。
6. 总结
- 闭包 = 函数 + 作用域链
- 能够访问外部函数的变量,即使外部函数已经执行结束。
- 常用于:
- 数据封装与私有化
- 延迟执行或回调
- 维护状态(计数器、缓存等)
- 使用闭包时要注意:
- 内存泄漏:避免持有不必要的引用
- 循环变量陷阱:使用
let或 IIFE 避免共享同一变量 - 性能开销:避免大量动态创建闭包
闭包是 JavaScript 核心概念之一,理解闭包对于模块化、函数式编程以及异步编程非常关键。
7. 练习题
- 使用闭包实现一个计数器,支持
add()和reset()方法。 - 写一个函数,返回一个数组,每个元素是 0~4 的平方值,要求使用闭包解决循环变量问题。
- 分析下面代码输出:
1 | function createFunctions() { |
8. 面试题解析
题目分析:
上面的代码中,使用 var 声明循环变量 i,所有函数都共享同一个 i,循环结束时 i = 3,所以三次调用输出都是 3。
解决方法 1:使用 let
1 | function createFunctions() { |
let 每次循环都会创建新的块级作用域,闭包绑定的是每次循环的 i。
解决方法 2:使用 IIFE(立即执行函数表达式)
1 | function createFunctions() { |
通过 IIFE 将当前循环变量 i 传入参数 j,每次闭包绑定的都是独立的值。
总结面试要点:
- 闭包常考点:私有变量、状态保存、异步回调。
- 循环中闭包容易出现共享同一变量的问题。
- 面试时应能解释
var与let的区别,以及如何使用 IIFE 或块级作用域解决闭包问题。