每日一报
闭包 (Closure)
闭包的本质可以理解为 javascript
的词法作用域规则 和 函数作为一等公民(可以作为值传递和返回) 这两个特性的自然结果。它并不是一个需要显式声明的东西,而是满足特定条件时的一种现象或机制。
关键机制
具体来说,闭包的形成和工作依赖于以下关键机制:
函数记住定义时环境
- 当一个函数(我们称之为 内部函数)在另一个函数(外部函数)内部 被定义 时,当执行外部函数时,引擎会创建一个代表声明内部函数的 函数对象。
- 这个函数对象通过其内部的
[[Environment]]
插槽(可以理解为一个内部链接)记住并持有一个指向其定义时所在的词法环境(即外部函数的词法环境)的引用。这个环境包含了外部函数当时的变量等信息。
保持环境存活
- 如果这个内部函数在外部函数执行完毕后 仍然可达(例如,被外部函数
return
出来并赋值给一个变量,或者作为回调函数/事件处理器传递出去),那么对这个内部函数对象的引用就会像一根绳子,阻止javascript
的垃圾回收机制回收那个被它[[Environment]]
引用的 外部函数的词法环境。 - 即使外部函数的执行上下文已经销毁,但它当时的词法环境(包含其内部变量)会因为被内部函数引用而 得以保留 在内存中。
跨作用域访问
- 当这个内部函数 稍后被调用 时(可能在完全不同的作用域和执行上下文中):
- 会为这次调用创建一个 新的、临时的、内部的 执行上下文和对应的 内部词法环境(用于存放这次调用的参数和内部函数自己声明的局部变量)。
- 这个新的内部词法环境的 外部环境引用 (
outer
) 会被设置为指向该内部函数对象[[Environment]]
所引用的那个被保留下来的、定义时的外部词法环境。 - 这就构成了作用域链:
[本次调用的内部环境] -> [函数定义时的外部环境] -> [更外层的环境] -> ... -> [全局环境]
。 - 因此,内部函数在执行时,可以沿着这条作用域链 访问并操作 那个在其定义时存在、并且被 封闭 保留下来的外部词法环境中的变量。
- 这个新的内部词法环境的 外部环境引用 (
- 会为这次调用创建一个 新的、临时的、内部的 执行上下文和对应的 内部词法环境(用于存放这次调用的参数和内部函数自己声明的局部变量)。
复用已创建的词法环境
- 复用的是
定义时
的外部环境: 闭包的核心在于 复用 了那个 内部函数定义时所在的外部词法环境。正是因为这个环境被 保留 和 复用,才使得外部函数的变量能够在多次调用内部函数之间保持其状态。 - 不复用
调用时
的内部环境: 对于 内部函数每次调用时 创建的 内部词法环境,它 并不会被复用。每次调用内部函数都会创建一个 全新的、独立的 内部词法环境。
本质描述
闭包的本质是:一个函数(内部函数)与其被创建时所在的词法环境(定义时的环境)的 组合
。这个组合使得该函数即使在其定义的词法环境之外被执行时,也能够访问和操作其定义时环境中的变量。
这种机制的核心在于:函数对象通过其 [[Environment]]
内部插槽 持久化了对其定义时作用域链的引用,阻止了该作用域链上的相关环境(主要是其定义的外部环境)被垃圾回收,从而实现了跨执行上下文的变量访问和状态保持。