本文为《人人都能读标准》—— ECMAScript篇的第10篇。我在这个仓库中系统地介绍了标准的阅读规则以及使用方式,并深入剖析了标准对JavaScript核心原理的描述。
在8.执行环境我们说过,由ECMAScript代码创建的执行上下文会有一个词法环境的组件,指向一个环境记录器,这是每一条作用域链的起点。
在9.作用域我们提到,每一个环境记录器都有一个[[OuterEnv]]
字段指向另一个环境记录器,通过[[OuterEnv]]
连接起来的所有环境记录器共同构成一条作用域链。并且,在那一节中我们已经多多少少已经见过作用域链在环境中的样子了:
for (var i = 1; i < 5; i++) { setTimeout(() => { console.log(i) // ① }, i * 1000) }
第一次执行到位置 ①,调用栈如下所示:
本节,我会先讲标识符解析的算法,即在作用域链上查找标识符的具体过程;然后,我会讲作用域链的构建过程;最后,我会讲一个以作用域链为基础、且大家都非常关心的概念 —— 闭包。
标识符的解析
在执行代码的过程中,当需要解析某个标识符的时候,会获取当前执行上下文的词法环境,从词法环境指向的环境记录器开始,沿着作用域链依次查找标识符,直到找到标识符或者走完整条作用域链才结束。
我们可以从标识符identifier的求值语义看到详细的过程:
以下我为你把这个过程使用中文进行概括(name
为标识符):
- 执行ResolveBinding(
name
):
- 默认把变量
env
设置为当前执行上下文的词法环境;(①)- 此时
env
是一个环境记录器;- 如果处于严格模式,设置变量
strict
为true,否则,设置为false;- 执行:GetIdentifierReference(env, name,strict):
- 如果
env
为null,返回一个表示解析失败的引用记录器。- 调用env.HasBinding(name),查看
name
是否存在环境中:(②)- 如果结果为true,返回一个表示该标识符的引用记录器(Reference Record);(③)
- 如果结果为false,通过
env.[[OuterEnv]]
获得外层环境记录器outer
,然后递归地调用GetIdentifierReference(outer, name,strict);(