JavaScript引擎在块无词法声明时仍会创建块作用域吗?
var声明的执行细节疑问 众所周知,只有let和const具备块作用域,但根据ECMAScript规范中的BlockEvaluation流程,每当块被求值时,都会通过BlockDeclarationInstantiation创建新的块作用域:
- 令oldEnv为当前执行上下文的LexicalEnvironment。
- 令blockEnv为NewDeclarativeEnvironment(oldEnv)。
- 执行BlockDeclarationInstantiation(StatementList, blockEnv)。
- 将当前执行上下文的LexicalEnvironment设置为blockEnv。
- 令blockValue为StatementList求值的Completion结果。
- 将当前执行上下文的LexicalEnvironment恢复为oldEnv。
- 返回? blockValue。
而BlockDeclarationInstantiation中规定:
- 令declarations为代码的LexicallyScopedDeclarations。
针对如下代码:
function foo() { if (true) { var x = 1 } }
按照步骤2,块开始求值时会创建块作用域,但我的假设是否正确:执行时会创建新块作用域,但不会将var变量注册到该环境记录,随后丢弃该作用域?或是因为编译阶段已生成AST,引擎提前知晓块内仅含var声明,从而跳过创建块作用域?
我原本期望在执行NewDeclarativeEnvironment前,会先检查是否存在LexicallyScopedDeclarations。
在AST生成的JSON中可见,块语句内包含一个var变量声明,似乎JS引擎在求值阶段确实创建了该块作用域,随后发现是var声明而未将其注册到该作用域。
{ "type": "Program", "start": 0, "end": 50, "body": [ { "type": "FunctionDeclaration", "start": 0, "end": 50, "id": { "type": "Identifier", "start": 9, "end": 12, "name": "foo" }, "expression": false, "generator": false, "async": false, "params": [], "body": { "type": "BlockStatement", "start": 15, "end": 50, "body": [ { "type": "IfStatement", "start": 19, "end": 48, "test": { "type": "Literal", "start": 23, "end": 27, "value": true, "raw": "true" }, "consequent": { "type": "BlockStatement", "start": 29, "end": 48, "body": [ { "type": "VariableDeclaration", "start": 35, "end": 44, "declarations": [ { "type": "VariableDeclarator", "start": 39, "end": 44, "id": { "type": "Identifier", "start": 39, "end": 40, "name": "x" }, "init": { "type": "Literal", "start": 43, "end": 44, "value": 1, "raw": "1" } } ], "kind": "var" } ] }, "alternate": null } ] } } ], "sourceType": "module" }
回答
你的第一个假设是正确的:引擎确实会创建新的块作用域,但不会将var变量注册到这个块环境记录中,最终这个空的块作用域会被丢弃。
原因在于规范定义的LexicallyScopedDeclarations仅包含let、const、class这类块级声明,var声明属于VarScopedDeclarations,不会被纳入BlockDeclarationInstantiation的处理范围。所以当执行到块求值步骤时,流程会严格按照规范走:先创建新的声明式环境(块作用域),但在执行BlockDeclarationInstantiation时,因为当前块的LexicallyScopedDeclarations为空,不会有任何变量被注册到这个新环境里。后续块内的var x = 1会被提升到函数作用域,而不是当前块作用域。
至于引擎是否会优化跳过创建空块作用域?规范并没有要求必须这么做,但实际引擎可能会做这类优化——不过从规范的语义角度来说,必须保证块作用域的创建流程被执行,即使最终没有变量绑定到这个作用域。你的AST结果也能佐证这一点:块语句的结构在AST中被保留,说明编译阶段没有跳过块的处理,求值阶段会按规范创建块作用域,只是后续没有变量绑定到这个作用域而已。
内容的提问来源于stack exchange,提问作者dbzx10299

