JavaScript中闭包确定时机及[[scopes]]等相关作用域问题咨询
闭包与JavaScript作用域核心问题解析
1. 闭包的形成时机:运行时,而非编译时
闭包的形成完全是运行时的行为,结合你的示例代码,我们顺着执行上下文和执行栈的流程拆解:
全局编译与初始化:
全局代码编译时,函数a会被声明,此时引擎仅记录它的词法定义位置(全局环境),但还没有闭包——闭包需要函数实际执行、且捕获的外部环境被保留才能形成。调用函数a,创建执行上下文:
执行let c = a();时,函数a的执行上下文被创建并推入执行栈。这个上下文包含:- 自身的词法环境,存储局部变量
t=0 - 外部环境引用,指向全局词法环境
当a返回内部函数b时,函数b被实例化,此时引擎会让b的内部属性[[Scopes]]保留对a的词法环境的引用——这是闭包的关键伏笔,但此时还不算闭包。
- 自身的词法环境,存储局部变量
a执行完毕,闭包正式形成:
a执行完成后,它的执行上下文被弹出执行栈。按照常规垃圾回收逻辑,a的词法环境(包含t)应该被销毁,但因为b还持有这个环境的引用,所以它会被保留下来。此时,b作为能访问已销毁函数局部变量的函数,就成为了闭包。调用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,提问作者江定玺
相关产品推荐
相关产品推荐

