Skip to content

每日一报

闭包 (Closure)

闭包的本质可以理解为 javascript 的词法作用域规则函数作为一等公民(可以作为值传递和返回) 这两个特性的自然结果。它并不是一个需要显式声明的东西,而是满足特定条件时的一种现象或机制。

关键机制

具体来说,闭包的形成和工作依赖于以下关键机制:

函数记住定义时环境

  • 当一个函数(我们称之为 内部函数)在另一个函数(外部函数)内部 被定义 时,当执行外部函数时,引擎会创建一个代表声明内部函数的 函数对象
  • 这个函数对象通过其内部的 [[Environment]] 插槽(可以理解为一个内部链接)记住并持有一个指向其定义时所在的词法环境(即外部函数的词法环境)的引用。这个环境包含了外部函数当时的变量等信息。

保持环境存活

  • 如果这个内部函数在外部函数执行完毕后 仍然可达(例如,被外部函数 return 出来并赋值给一个变量,或者作为回调函数/事件处理器传递出去),那么对这个内部函数对象的引用就会像一根绳子,阻止 javascript 的垃圾回收机制回收那个被它 [[Environment]] 引用的 外部函数的词法环境
  • 即使外部函数的执行上下文已经销毁,但它当时的词法环境(包含其内部变量)会因为被内部函数引用而 得以保留 在内存中。

跨作用域访问

  • 当这个内部函数 稍后被调用 时(可能在完全不同的作用域和执行上下文中):
    • 会为这次调用创建一个 新的临时的内部的 执行上下文和对应的 内部词法环境(用于存放这次调用的参数和内部函数自己声明的局部变量)。
      • 这个新的内部词法环境的 外部环境引用 (outer) 会被设置为指向该内部函数对象 [[Environment]] 所引用的那个被保留下来的、定义时的外部词法环境
      • 这就构成了作用域链[本次调用的内部环境] -> [函数定义时的外部环境] -> [更外层的环境] -> ... -> [全局环境]
      • 因此,内部函数在执行时,可以沿着这条作用域链 访问并操作 那个在其定义时存在、并且被 封闭 保留下来的外部词法环境中的变量

复用已创建的词法环境

  • 复用的是 定义时 的外部环境: 闭包的核心在于 复用 了那个 内部函数定义时所在的外部词法环境。正是因为这个环境被 保留复用,才使得外部函数的变量能够在多次调用内部函数之间保持其状态。
  • 不复用 调用时 的内部环境: 对于 内部函数每次调用时 创建的 内部词法环境,它 并不会被复用。每次调用内部函数都会创建一个 全新的独立的 内部词法环境。

本质描述

闭包的本质是:一个函数(内部函数)与其被创建时所在的词法环境(定义时的环境)的 组合。这个组合使得该函数即使在其定义的词法环境之外被执行时,也能够访问和操作其定义时环境中的变量。

这种机制的核心在于:函数对象通过其 [[Environment]] 内部插槽 持久化了对其定义时作用域链的引用,阻止了该作用域链上的相关环境(主要是其定义的外部环境)被垃圾回收,从而实现了跨执行上下文的变量访问和状态保持。

Contributors

Changelog

Discuss

Released under the CC BY-SA 4.0 License. (2619af4)