You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaScript中闭包确定时机及[[scopes]]等相关作用域问题咨询

闭包与JavaScript作用域核心问题解析

1. 闭包的形成时机:运行时,而非编译时

闭包的形成完全是运行时的行为,结合你的示例代码,我们顺着执行上下文和执行栈的流程拆解:

  1. 全局编译与初始化:
    全局代码编译时,函数a会被声明,此时引擎仅记录它的词法定义位置(全局环境),但还没有闭包——闭包需要函数实际执行、且捕获的外部环境被保留才能形成。

  2. 调用函数a,创建执行上下文:
    执行let c = a();时,函数a的执行上下文被创建并推入执行栈。这个上下文包含:

    • 自身的词法环境,存储局部变量t=0
    • 外部环境引用,指向全局词法环境
      当a返回内部函数b时,函数b被实例化,此时引擎会让b的内部属性[[Scopes]]保留对a的词法环境的引用——这是闭包的关键伏笔,但此时还不算闭包。
  3. a执行完毕,闭包正式形成:
    a执行完成后,它的执行上下文被弹出执行栈。按照常规垃圾回收逻辑,a的词法环境(包含t)应该被销毁,但因为b还持有这个环境的引用,所以它会被保留下来。此时,b作为能访问已销毁函数局部变量的函数,就成为了闭包。

  4. 调用c()(即b),验证闭包:
    执行c();时,b的执行上下文被推入栈,它的词法环境的外部引用指向a的保留环境,因此能找到t并执行t++,打印出1——这就是闭包的实际生效。

简言之:编译阶段只记录函数的词法作用域信息,闭包是在运行时,当内部函数被返回并持有外部函数的词法环境引用,且外部函数执行完毕后才真正形成的。

2. [[Scopes]]属性与作用域链的关系

  • 所有函数都天生具备[[Scopes]]:这是ECMAScript规范定义的内部槽(internal slot),函数在定义时就会被初始化。它本质是一个链表,存储函数定义时所在的所有外部词法环境的引用。
  • [[Scopes]]是外部词法环境引用的静态集合:函数定义时,引擎会把当前的外部词法环境依次加入[[Scopes]],比如你的示例中,b定义在a内部,所以b的[[Scopes]]就是[a的词法环境, 全局词法环境]。
  • 作用域链由[[Scopes]]和当前执行上下文的词法环境共同决定:当函数被调用时,会创建自己的词法环境,这个环境的outer(外部引用)指向[[Scopes]]的第一个节点,然后当前词法环境 → outer链就构成了作用域链。变量查找时,会沿着这个链从内到外查找。
  • 作用域链的决定者是词法结构:也就是你写代码时函数的嵌套位置,和函数的调用位置无关——这就是词法作用域的核心。

核心概念关联梳理

把执行上下文、执行栈、词法环境、闭包、[[Scopes]]的关系用直白逻辑串起来:

  • 执行栈:管理执行上下文的"容器",全局上下文先入栈,函数调用时生成对应上下文入栈,执行完出栈。
  • 执行上下文:函数调用时临时生成的环境,包含词法环境(处理let/const)、变量环境(处理var)、this绑定,是函数执行的基础。
  • 词法环境:每个执行上下文的核心部分,分为两部分:①环境记录(存储当前环境的变量/函数);②外部环境引用(指向外部的词法环境)。函数定义时的外部环境会被记录到[[Scopes]]。
  • [[Scopes]]:函数的静态内部属性,存储函数定义时的外部词法环境引用链,是作用域链的"蓝图"。
  • 作用域链:函数执行时,由当前词法环境 + [[Scopes]]构成的链式结构,用于变量查找。
  • 闭包:当函数的[[Scopes]]中引用的外部词法环境,在函数调用时已经被弹出执行栈(外部函数执行完毕),但因为函数仍持有该环境的引用,导致环境无法被垃圾回收,此时这个函数就是闭包——本质是"函数 + 它捕获的外部词法环境"的组合。

内容的提问来源于stack exchange,提问作者江定玺

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 00:40:28