ECMAScript 6 块级绑定

ES 6 引入了块级作用域的概念。

Block:{StatementList} 执行过程伪代码描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function blockEvaluation(StatementList) {
let oldEnv = runningExecutionContext.LexicalEnvironment;
let blockEnv = new DeclarativeEnvironment(oldEnv);
BlockDeclarationInstantiation(StatementList, blockEnv);
runningExecutionContext.LexicalEnvironment = blockEnv;
let blockValue = evaluate(StatementList);
runningExecutionContext.LexicalEnvironment = oldEnv;
return blockValue;
}

function BlockDeclarationInstantiation (code, env) {
let envRec = env.EnvironmentRecord;
Assert(envRec instanceof DeclarativeEnvironment);
let declarations = code.LexicallyScopedDeclarations;
for (let d of declarations) {
for (let dn of d.BoundNames) {
if (d.IsConstantDeclaration === true) {
envRec.CreateImmutableBinding(dn, true);
} else {
envRec.CreateMutableBinding(dn, false);
}
}
if (d instanceof GeneratorDeclaration || d instanceof FunctionDeclaration) {
let fn = d.BoundNames;
let fo = InstantiateFunctionObject(env);
envRec.InitializeBinding(fn, fo)
}
}
}

从上面代码可以看出(忽略 label 语句):执行块初始化绑定,只对声明语句中的声明进行初始化,并不递归处理语句。!此处的声明语句不包括 var 声明语句。

1
2
3
4
5
6
7
(function(x, f = () => x) {
var x;
var y = x;
x = 2;
return [x, y, f()];
})(1)
// [2, 1, 1]

为了解释这段代码的执行结果,我们从 函数对象 F 的 [[Call]] 内部方法说起。F.[[Call]] 接收两个参数 thisArgument 和 argumentsList,它的执行步骤如下(简单描述):

  1. 准备环境
  2. 绑定this
  3. 执行 OrdinaryCallEvaluateBody(F, argumentsList)
  4. 恢复环境

上面步骤中的第三步又会执行 FunctionDeclarationInstantiation(F, argumentsList) 来进行函数声明初始化。这里的步骤很多(详见规范 9.2.12),简单描述下(忽略参数有重复情况):

  1. 首先,将所有的函数(Function and Generator)声明 收集到 functionsToInitialize 。如果有多个同名函数,采用最后一个。
  2. 对所有形参,创建绑定
  3. 判断是否需要创建 arguments 对象。 三种情形除外:箭头函数,形参已含有 arguments,参数没有表达式并且 arguments 被占用(被 function | generator | let | const | class 声明占用)
  4. 根据是否严格模式,创建不同的 arguments 对象,并设置绑定初始化。
  5. 令 iteratorRecord 为以 arguments 为参数创建的迭代对象。 执行 IteratorBindingInitialization(iteratorRecord, env)
  6. 如果参数中没有表达式,对每个没有实例化的 VarDeclaredNames,创建绑定,并初始化为 undefined
  7. 否则,参数有表达式,以当前的环境为参数创建一个声明式环境 varEnv。对 VarDeclaredNames 中每个没有实例化的元素 n :
    1. 如果 n 不在参数名列表中,或者 n 是一个函数名 设置初始值为 undefined
    2. 否则,初始值从原环境记录项 envRec 中获取。
    3. 注意( 也就是说,如果 var 变量与形参同名,初始化时也有相同的值。)
  8. 如果不是 严格模式,再创建一个声明式环境,改变原来的词法环境
  9. 对所有的词法声明,在词法环境记录项上创建绑定。(注意,在此并未初始化)
  10. 对 functionsToInitialize 中的函数,实例化函数对象(以词法环境为参数)并在变量环境记录项上设置绑定值。

VarDeclaredNames : FunctionDeclaration、 GeneratorDeclaration and VariableDeclaration
LexicallyDeclaredNames : let 、const and class

IteratorBindingInitialization
遍历处理所有形参,如果有初始值,并且形参未设置。对右侧求值,并设置形参的初始化值。