前言
ECMAScript标准是深入学习JavaScript原理最好的资料,没有其二。
通过增加对ECMAScript语言的理解,理解javascript现象后面的逻辑,提升个人编码能力。
欢迎关注和订阅专栏 **[重学前端-ECMAScript协议上篇]
示例
基于上个示例,做一些调整,下面的代码又是如何输出的呢,以及其背后执行上下文,环境记录又是怎样运作的呢。
xml
javascript
复制代码
<script>
"use strict"
var varA = 'varA';
const constA = 'constA';
{
var varA = 'block_varA';
let constA = 'block_constA';
}
function log(){
const constA = 'log_constA';
console.log(varA, constA);
}
log();
</script>
这段代码就得分两段来分析了
- log 方法调用前
- log 方法的执行时
全局代码
代码执行的基本流程
和之前的基本逻辑完全一样
-
16.1.5 ParseScript ( sourceText, realm, hostDefined )
script 的源码转为 脚本记录。
-
16.1.6 ScriptEvaluation ( scriptRecord ) 申明实例化 + 代码执行
- 16.1.7 GlobalDeclarationInstantiation ( script, env )
实例化全局变量和函数声明 - Evaluation of script
全局顶层代码执行。
- 16.1.7 GlobalDeclarationInstantiation ( script, env )
ParseScript
返回的脚本记录的[[ECMAScriptCode]] 大致的内容如下:
json
javascript
复制代码
{
"type": "Script",
"location": {
"startIndex": 2,
"endIndex": 229,
"start": {
"line": 1,
"column": 3
},
"end": {
"line": 14,
"column": 9
}
},
"strict": false,
"ScriptBody": {
"type": "ScriptBody",
"location": {
"startIndex": 2,
"endIndex": 229,
"start": {
"line": 1,
"column": 3
},
"end": {
"line": 14,
"column": 9
}
},
"strict": true,
"StatementList": [
{
"type": "ExpressionStatement",
"location": {
"startIndex": 2,
"endIndex": 14,
"start": {
"line": 1,
"column": 3
},
"end": {
"line": 1,
"column": 3
}
},
"strict": true,
"Expression": {
"type": "StringLiteral",
"location": {
"startIndex": 2,
"endIndex": 14,
"start": {
"line": 1,
"column": 3
},
"end": {
"line": 1,
"column": 3
}
},
"strict": true,
"value": "use strict"
}
},
{
"type": "VariableStatement",
"location": {
"startIndex": 17,
"endIndex": 35,
"start": {
"line": 2,
"column": 3
},
"end": {
"line": 2,
"column": 20
}
},
"strict": true,
"VariableDeclarationList": [
{
"type": "VariableDeclaration",
"location": {
"startIndex": 21,
"endIndex": 34,
"start": {
"line": 2,
"column": 7
},
"end": {
"line": 2,
"column": 14
}
},
"strict": true,
"BindingIdentifier": {
"type": "BindingIdentifier",
"location": {
"startIndex": 21,
"endIndex": 25,
"start": {
"line": 2,
"column": 7
},
"end": {
"line": 2,
"column": 7
}
},
"strict": true,
"name": "varA"
},
"Initializer": {
"type": "StringLiteral",
"location": {
"startIndex": 28,
"endIndex": 34,
"start": {
"line": 2,
"column": 14
},
"end": {
"line": 2,
"column": 14
}
},
"strict": true,
"value": "varA"
}
}
]
},
{
"type": "LexicalDeclaration",
"location": {
"startIndex": 38,
"endIndex": 62,
"start": {
"line": 3,
"column": 3
},
"end": {
"line": 3,
"column": 26
}
},
"strict": true,
"LetOrConst": "const",
"BindingList": [
{
"type": "LexicalBinding",
"location": {
"startIndex": 44,
"endIndex": 61,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 18
}
},
"strict": true,
"BindingIdentifier": {
"type": "BindingIdentifier",
"location": {
"startIndex": 44,
"endIndex": 50,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 9
}
},
"strict": true,
"name": "constA"
},
"Initializer": {
"type": "StringLiteral",
"location": {
"startIndex": 53,
"endIndex": 61,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 18
}
},
"strict": true,
"value": "constA"
}
}
]
},
{
"type": "Block",
"location": {
"startIndex": 66,
"endIndex": 133,
"start": {
"line": 5,
"column": 3
},
"end": {
"line": 8,
"column": 3
}
},
"strict": true,
"StatementList": [
{
"type": "VariableStatement",
"location": {
"startIndex": 72,
"endIndex": 96,
"start": {
"line": 6,
"column": 5
},
"end": {
"line": 6,
"column": 28
}
},
"strict": true,
"VariableDeclarationList": [
{
"type": "VariableDeclaration",
"location": {
"startIndex": 76,
"endIndex": 95,
"start": {
"line": 6,
"column": 9
},
"end": {
"line": 6,
"column": 16
}
},
"strict": true,
"BindingIdentifier": {
"type": "BindingIdentifier",
"location": {
"startIndex": 76,
"endIndex": 80,
"start": {
"line": 6,
"column": 9
},
"end": {
"line": 6,
"column": 9
}
},
"strict": true,
"name": "varA"
},
"Initializer": {
"type": "StringLiteral",
"location": {
"startIndex": 83,
"endIndex": 95,
"start": {
"line": 6,
"column": 16
},
"end": {
"line": 6,
"column": 16
}
},
"strict": true,
"value": "block_varA"
}
}
]
},
{
"type": "LexicalDeclaration",
"location": {
"startIndex": 101,
"endIndex": 129,
"start": {
"line": 7,
"column": 5
},
"end": {
"line": 7,
"column": 32
}
},
"strict": true,
"LetOrConst": "let",
"BindingList": [
{
"type": "LexicalBinding",
"location": {
"startIndex": 105,
"endIndex": 128,
"start": {
"line": 7,
"column": 9
},
"end": {
"line": 7,
"column": 18
}
},
"strict": true,
"BindingIdentifier": {
"type": "BindingIdentifier",
"location": {
"startIndex": 105,
"endIndex": 111,
"start": {
"line": 7,
"column": 9
},
"end": {
"line": 7,
"column": 9
}
},
"strict": true,
"name": "constA"
},
"Initializer": {
"type": "StringLiteral",
"location": {
"startIndex": 114,
"endIndex": 128,
"start": {
"line": 7,
"column": 18
},
"end": {
"line": 7,
"column": 18
}
},
"strict": true,
"value": "block_constA"
}
}
]
}
]
},
{
"type": "FunctionDeclaration",
"location": {
"startIndex": 137,
"endIndex": 220,
"start": {
"line": 10,
"column": 3
},
"end": {
"line": 13,
"column": 3
}
},
"strict": true,
"BindingIdentifier": {
"type": "BindingIdentifier",
"location": {
"startIndex": 146,
"endIndex": 149,
"start": {
"line": 10,
"column": 12
},
"end": {
"line": 10,
"column": 12
}
},
"strict": true,
"name": "log"
},
"FormalParameters": [],
"FunctionBody": {
"type": "FunctionBody",
"location": {
"startIndex": 151,
"endIndex": 220,
"start": {
"line": 10,
"column": 17
},
"end": {
"line": 13,
"column": 3
}
},
"strict": true,
"directives": [],
"FunctionStatementList": [
{
"type": "LexicalDeclaration",
"location": {
"startIndex": 157,
"endIndex": 185,
"start": {
"line": 11,
"column": 5
},
"end": {
"line": 11,
"column": 32
}
},
"strict": true,
"LetOrConst": "const",
"BindingList": [
{
"type": "LexicalBinding",
"location": {
"startIndex": 163,
"endIndex": 184,
"start": {
"line": 11,
"column": 11
},
"end": {
"line": 11,
"column": 20
}
},
"strict": true,
"BindingIdentifier": {
"type": "BindingIdentifier",
"location": {
"startIndex": 163,
"endIndex": 169,
"start": {
"line": 11,
"column": 11
},
"end": {
"line": 11,
"column": 11
}
},
"strict": true,
"name": "constA"
},
"Initializer": {
"type": "StringLiteral",
"location": {
"startIndex": 172,
"endIndex": 184,
"start": {
"line": 11,
"column": 20
},
"end": {
"line": 11,
"column": 20
}
},
"strict": true,
"value": "log_constA"
}
}
]
},
{
"type": "ExpressionStatement",
"location": {
"startIndex": 190,
"endIndex": 216,
"start": {
"line": 12,
"column": 5
},
"end": {
"line": 12,
"column": 30
}
},
"strict": true,
"Expression": {
"type": "CallExpression",
"location": {
"startIndex": 190,
"endIndex": 215,
"start": {
"line": 12,
"column": 5
},
"end": {
"line": 12,
"column": 29
}
},
"strict": true,
"CallExpression": {
"type": "MemberExpression",
"location": {
"startIndex": 190,
"endIndex": 201,
"start": {
"line": 12,
"column": 5
},
"end": {
"line": 12,
"column": 13
}
},
"strict": true,
"MemberExpression": {
"type": "IdentifierReference",
"location": {
"startIndex": 190,
"endIndex": 197,
"start": {
"line": 12,
"column": 5
},
"end": {
"line": 12,
"column": 5
}
},
"strict": true,
"escaped": false,
"name": "console"
},
"IdentifierName": {
"type": "IdentifierName",
"location": {
"startIndex": 198,
"endIndex": 201,
"start": {
"line": 12,
"column": 13
},
"end": {
"line": 12,
"column": 13
}
},
"strict": true,
"name": "log"
},
"PrivateIdentifier": null,
"Expression": null
},
"Arguments": [
{
"type": "IdentifierReference",
"location": {
"startIndex": 202,
"endIndex": 206,
"start": {
"line": 12,
"column": 17
},
"end": {
"line": 12,
"column": 17
}
},
"strict": true,
"escaped": false,
"name": "varA"
},
{
"type": "IdentifierReference",
"location": {
"startIndex": 208,
"endIndex": 214,
"start": {
"line": 12,
"column": 23
},
"end": {
"line": 12,
"column": 23
}
},
"strict": true,
"escaped": false,
"name": "constA"
}
]
}
}
]
}
},
{
"type": "ExpressionStatement",
"location": {
"startIndex": 223,
"endIndex": 229,
"start": {
"line": 14,
"column": 3
},
"end": {
"line": 14,
"column": 8
}
},
"strict": true,
"Expression": {
"type": "CallExpression",
"location": {
"startIndex": 223,
"endIndex": 228,
"start": {
"line": 14,
"column": 3
},
"end": {
"line": 14,
"column": 7
}
},
"strict": true,
"CallExpression": {
"type": "IdentifierReference",
"location": {
"startIndex": 223,
"endIndex": 226,
"start": {
"line": 14,
"column": 3
},
"end": {
"line": 14,
"column": 3
}
},
"strict": true,
"escaped": false,
"name": "log"
},
"Arguments": []
}
}
]
}
}
去掉与本示例不太重要的节点,绘制成 tree,外加上对应的代码,如下:

整段代码也就六个语句
语句 | 说明 | 对应代码 |
---|---|---|
ExpressionStatement | 表达式语句 | "use strict" |
VariableStatement | 变量申明语句 | var varA = 'varA' ; |
LexicalDeclaration | 词法申明 | const constA = 'constA' ; |
Block | 块 | {var varA = 'block_varA';let constA = 'block_constA';} |
FunctionDeclaration | 函数申明 | function log(){const constA = 'log_constA';console.log(varA, constA);} |
ExpressionStatement | 表达式语句 | log(); |
解析变量申明和词法申明
VarDeclaredNames
唯一需要注意的是, 函数申明 在 脚本或者函数的顶层代码中的表现和变量申明是一致的,所以值为:
`'varA'`, `'varA'`, `'log'`
LexicallyDeclaredNames
Script和函数顶层词法申明标识符查找不会遍历 Block
类型的子节点, 所以返回值:
`constA`
申明初始化
全局环境记录又是由 对象环境记录 和 申明环境记录组成的, 词法申明和变量申明对应的绑定关系如下。
VarDeclaredNames | LexicallyDeclaredNames | |
---|---|---|
创建方法 | env.CreateGlobalVarBinding(var)env.CreateGlobalFunctionBinding (function) | env.CreateMutableBinding (let/class)env.CreateImmutableBinding(const) |
对象环境记录 | ✅ | |
申明环境记录 | ✅ |
从上面的解析变量申明和词法申明,得知结果:
- VarDeclaredNames : [
'varA'
,'varA'
,'log'
] - LexicallyDeclaredNames:[
'constA'
]
最后的关系图如下:

和之前的没什么不同,只是
- 全局对象环境记录 和 全局对象上 多了一个 log 属性,并且有值
- 全局环境记录 的VarNames 中多了个 log
function object
这里必须得提一下 log 的值 funciton object, 不然后面的逻辑没法继续,需要了解几点
- 什么时候被创建的?
- 其有一个
[[ECMAScriptCode]]
内部属性,是其内部代码的解析节点, 什么时候被赋值的? - 其有一个
[[Environment]]
内部属性,函数被定义时所处的环境记录。
什么时候被创建的呢?
在全局顶层代码 申明实例化的时候 GlobalDeclarationInstantiation ( script, env ) 16步骤
function object 的 [ECMAScriptCode]]
属性 什么时候被赋值的? 函数对象被创建的时就赋值了。(如果图片模糊,放大观看)


- 函数对象的
[[Environment]]
内部属性,指向当时关联的环境记录 , 这一点很重要, 很重要,真重要。

全局顶层代码执行
代码是怎么执行的呢? 协议一句 Evaluation of script, 详情参见单独的章节。

最终你可以简单理解为 就是 挨个 执行 Script 节点下的 StatementList,前面已经罗列了,本示例一共六个语句。
伪代码如下:
scss
javascript
复制代码
// ScriptBody : StatementList
function Evaluate_ScriptBody(ScriptBody) {
return Evaluate_StatementList(ScriptBody.StatementList);
}
就直接跳到 log();
这个语句前,最后执行上下文,环境记录和标识符绑定的关系图如下:
主要就是标识符都有值了。

log 函数执行
最后一个语句是函数调用,其解析节点信息如下,
json
javascriptjavascript
复制代码
{
"type": "ExpressionStatement",
"location": {
"startIndex": 223,
"endIndex": 229,
"start": {
"line": 14,
"column": 3
},
"end": {
"line": 14,
"column": 8
}
},
"strict": true,
"Expression": {
"type": "CallExpression",
"location": {
"startIndex": 223,
"endIndex": 228,
"start": {
"line": 14,
"column": 3
},
"end": {
"line": 14,
"column": 7
}
},
"strict": true,
"CallExpression": {
"type": "IdentifierReference",
"location": {
"startIndex": 223,
"endIndex": 226,
"start": {
"line": 14,
"column": 3
},
"end": {
"line": 14,
"column": 3
}
},
"strict": true,
"escaped": false,
"name": "log"
},
"Arguments": []
}
}
执行逻辑就是拿到 ExpressionStatement 的 Expression 进行调用,Expression节点类型为 CallExpression,调用的基本逻辑用伪代码编写如下
csharp
javascript
复制代码
function* Evaluate_CallExpression(CallExpression) {
// type 为 CallExpression 节点
const expr = CallExpression;
// type 为 "IdentifierReference" 节点,后面会用 其 name的值即log,查找函数对象
const memberExpr = expr.CallExpression;
// 参数列表
const args = expr.Arguments;
// 通过标志符名,查找函数对象 对应的引用记录
let ref = yield* ResolveBinding(memberExpr.name, undefined, memberExpr.strict);
// 从引用记录中 取值,获取函数对象 即 log 函数
let func = GetValue(ref);
// 是不是 处于尾部位置
const tailCall = IsInTailPosition();
// 执行 函数调用
return yield* EvaluateCall(func, ref, args, tailCall);
}
本章节主要是说作用域和作用域链,函数的申明实例化和调用过程,不是本节的主要目的。 但还是得说明几点
- log标志符绑定: 全局顶层代码 申明实例化的时候,而且其对应的值 函数对象也被初始化好了,所以这里可以通过上下文通过标志符查找到,然后进行调用。
- 函数调用是会创建新的执行上下文的
- 函数调用默认会新建一个函数环境记录,但是会根据函数 是不是有参数表达式,是不是严格模式会不创建或者创建不同个数的环境记录。(详情参见 函数调用的章节)
PrepareForOrdinaryCall 新建 函数执行上下文 和 函数环境记录
函数执行上下文和函数环境记录都是在 函数调用准备工作的 PrepareForOrdinaryCall创建的。

请记住第六步的这个操作 6. Set env.[[OuterEnv]] to F.[[Environment]].
这是形成链的操作,也是实现闭包的核心。
当 PrepareForOrdinaryCall执行完毕之后 ,因为创建了新的执行上下文和环境记录,执行上下文,环境记录的关系图就变成下面的样子了:

log 函数申明实例化
可能有同志注意到了,函数首行有 "use strict"
,这是为了简单 函数申明实例化过程,让其不去创建新的 环境记录。
javascript
javascript
复制代码
function log(){
"use strict"
const constA = 'log_constA';
console.log(varA, constA);
}
简单分析一下,得出
标志符 | 值 |
---|---|
VarDeclaredNames | [] |
LexicallyDeclaredNames | [constA ] |
函数执行是一个特别的标志符,就是 arguments
,
- 不属于 VarDeclaredNames 也不属于 LexicallyDeclaredNames 的结果中
- 可能是可变的,也可能是不可变的
函数申明实例化的具体逻辑 是 10.2.11 FunctionDeclarationInstantiation ( func, argumentsList ) 定义的,流程大致有近30步骤,非常复杂。
但是,本示属于 严格模式 + 无函数参数表达式 的搭配,不会再新建任何环境记录, arguments 和 constA 的绑定关系都存在于 函数环境记录上。
申明实例化完毕后, 执行上下文和环境记录关系图如下:
主要就是函数环境记录上多了两个绑定关系。

log 函数代码执行 和 标志符绑定查找
代码的执行本质就是 log 标志符对应的 function object ,执行其 [[ECMAScriptCode]]
属性解析节点 的过程,其节点信息如下:
json
javascript
复制代码
{
"type": "FunctionBody",
"location": {
"startIndex": 151,
"endIndex": 220,
"start": {
"line": 10,
"column": 17
},
"end": {
"line": 13,
"column": 3
}
},
"strict": true,
"directives": [],
"FunctionStatementList": [
{
"type": "LexicalDeclaration",
"location": {
"startIndex": 157,
"endIndex": 185,
"start": {
"line": 11,
"column": 5
},
"end": {
"line": 11,
"column": 32
}
},
"strict": true,
"LetOrConst": "const",
"BindingList": [
{
"type": "LexicalBinding",
"location": {
"startIndex": 163,
"endIndex": 184,
"start": {
"line": 11,
"column": 11
},
"end": {
"line": 11,
"column": 20
}
},
"strict": true,
"BindingIdentifier": {
"type": "BindingIdentifier",
"location": {
"startIndex": 163,
"endIndex": 169,
"start": {
"line": 11,
"column": 11
},
"end": {
"line": 11,
"column": 11
}
},
"strict": true,
"name": "constA"
},
"Initializer": {
"type": "StringLiteral",
"location": {
"startIndex": 172,
"endIndex": 184,
"start": {
"line": 11,
"column": 20
},
"end": {
"line": 11,
"column": 20
}
},
"strict": true,
"value": "log_constA"
}
}
]
},
{
"type": "ExpressionStatement",
"location": {
"startIndex": 190,
"endIndex": 216,
"start": {
"line": 12,
"column": 5
},
"end": {
"line": 12,
"column": 30
}
},
"strict": true,
"Expression": {
"type": "CallExpression",
"location": {
"startIndex": 190,
"endIndex": 215,
"start": {
"line": 12,
"column": 5
},
"end": {
"line": 12,
"column": 29
}
},
"strict": true,
"CallExpression": {
"type": "MemberExpression",
"location": {
"startIndex": 190,
"endIndex": 201,
"start": {
"line": 12,
"column": 5
},
"end": {
"line": 12,
"column": 13
}
},
"strict": true,
"MemberExpression": {
"type": "IdentifierReference",
"location": {
"startIndex": 190,
"endIndex": 197,
"start": {
"line": 12,
"column": 5
},
"end": {
"line": 12,
"column": 5
}
},
"strict": true,
"escaped": false,
"name": "console"
},
"IdentifierName": {
"type": "IdentifierName",
"location": {
"startIndex": 198,
"endIndex": 201,
"start": {
"line": 12,
"column": 13
},
"end": {
"line": 12,
"column": 13
}
},
"strict": true,
"name": "log"
},
"PrivateIdentifier": null,
"Expression": null
},
"Arguments": [
{
"type": "IdentifierReference",
"location": {
"startIndex": 202,
"endIndex": 206,
"start": {
"line": 12,
"column": 17
},
"end": {
"line": 12,
"column": 17
}
},
"strict": true,
"escaped": false,
"name": "varA"
},
{
"type": "IdentifierReference",
"location": {
"startIndex": 208,
"endIndex": 214,
"start": {
"line": 12,
"column": 23
},
"end": {
"line": 12,
"column": 23
}
},
"strict": true,
"escaped": false,
"name": "constA"
}
]
}
}
]
}
本质上也就是执行 两个 FunctionStatementList 的 表达式语句
表达式 | 代码 |
---|---|
LexicalDeclaration | const constA = 'log_constA'; |
ExpressionStatement | console.log(varA, constA); |
const constA = 'log_constA';
执行完毕之后,函数环境记录 constA 就有值 constA
。

console.log(varA, constA);
本质也是函数调用,不过 console.log
是内置函数,和用户自定义的函数是有一定的区别,不过这不是本章节的重点,本章节重点是作用域链。
先看看 varA
的查找路径,就是红色标出来的路径。
函数环境记录 通过 [[outetEnv]]
链接外部的全局环境记录,形成了链条一般的关系,这就是作用域链的直观解释。 ****

再看看 constA
的查找路径,就是红色标出的路径。
从下面的关系也可以知道
- log 函数内的 constA ,在外面 Script执行上下文 里面的脚本 是根本访问不到的
- log 函数 却能 访问全局环境记录中的 varA
这就是作用域的直观表示, 只能在一定的链中可以被访问到。

Block
到目前为止,都没细说过 块(Block) 里面的代码。 ES6 之后, 块里面的词法申明,是只能在块中被访问的。
比如如下代码:
xml
javascript
复制代码
<script>
"use strict"
var varA = 'varA';
{
var varA = 'block_varA';
function constA(){};
console.log(varA, constA); // block_varA ƒ constA() { }
}
console.log(varA, constA); // Uncaught ReferenceError: constA is not defined
</script>
至于全局顶层代码的 console.log(varA, constA)
为什么会报错,理解 Block 的 运作逻辑 是非要必要的,对应着协议的 14.2 Block 的子章节 14.2.2 Runtime Semantics: Evaluation 。
Block 会新建申明环境记录
Block 的评估过程,会新建申明环境记录 , 作用嘛, 用于保存 词法申明 对应的绑定关系,就是 const/let/class等的申明。 至于块内变量申明 (var/function等),其是不会保存的。

特别注意:
- 第4步骤,执行上下文把
LexicalEnvironment
指向了新的申明环境记录, 第6步骤进行了复原 - 从头到尾,执行上下文 没有更改
VariableEnvironment
的指向。
Block 的 申明实例化
申明实例化的逻辑在协议的 14.2.3 BlockDeclarationInstantiation ( code, env ) 。

实际上逻辑非常简单,就是先拿到所有的词法申明节点,然后创建绑定关系。这个 LexicallyScopedDeclarations
和 LexicallyDeclaredNames 类似, 不过返回的是 解析节点(Parse Node)而不是 标志符名。
之前有多次提到, LexicallyDeclaredNames 在Script 和 函数顶层代码是不包含 函数申明的,函数申明会作为变量申明处理。
重点来了, Block 块里面的代码,是不是顶层代码? 答案: 不是
因此,这里 LexicallyDeclaredNames 和 LexicallyScopedDeclaration 是包含 函数申明的。
Block 进入前后 作用域链的变化
通过列出三种状态的关系图,一起来看作用域的变化
- 进入Block 之前
- Block 内执行
console.log(varA, constA)
前 - 退出Block后,顶层代码执行
console.log(varA, constA)
前
进入Block之前

Block 内 console.log(varA, constA)
执行前
新建了申明环境记录,执行上文的 LexicalEnvironment
指向了 新的申明环境。
-
varA的查找:
- 查找
LexicalEnvironment
链路 未找到, - 再查找
VariableEnvironment
链路,找到。
- 查找
-
constA的查找:
- 查找
LexicalEnvironment
链路 ,在 Block块创建的申明环境记录中找到
- 查找

退出Block后,顶层代码执行 console.log(varA, constA)
前
重新更改了执行上下的 LexicalEnvironment
指向,恢复指向全局环境记录。
-
varA的查找:
- 查找
LexicalEnvironment
链路 未找到, - 再查找
VariableEnvironment
链路,找到。
- 查找
-
constA的查找:
- 查找
LexicalEnvironment
链路 未找到 - 再查找
VariableEnvironment
链路,未找到。
- 查找

小结
-
Block 块 会新建环境记录,不会新建执行上下文
-
通过上下文和环境记录的更改,实现 "动态" 的作用域链。
- 更改执行上下文 执行上下文
LexicalEnvironment
和VariableEnvironment
的指向 - 以及修改环境记录的
[[OuterEnv]]
的指向
- 更改执行上下文 执行上下文
-
Block 块 内的函数申明,属于词法申明 (起码严格模式是)
思考
对本文开头的代码稍微做点修改,删除顶部的 "use Strict"
, 当 log
函数执行到 console.log(varA, constA);
。
执行上下文,环境记录,标志符绑定又是怎样的呢?
ini
html
复制代码
<script>
var varA = 'varA';
const constA = 'constA';
{
var varA = 'block_varA';
let constA = 'block_constA';
}
function log(){
const constA = 'log_constA';
console.log(varA, constA);
}
log();
</script>
上一章
下一章