You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaScript引擎在块无词法声明时仍会创建块作用域吗?

关于块作用域与var声明的执行细节疑问

众所周知,只有let和const具备块作用域,但根据ECMAScript规范中的BlockEvaluation流程,每当块被求值时,都会通过BlockDeclarationInstantiation创建新的块作用域:

  1. 令oldEnv为当前执行上下文的LexicalEnvironment。
  2. 令blockEnv为NewDeclarativeEnvironment(oldEnv)。
  3. 执行BlockDeclarationInstantiation(StatementList, blockEnv)。
  4. 将当前执行上下文的LexicalEnvironment设置为blockEnv。
  5. 令blockValue为StatementList求值的Completion结果。
  6. 将当前执行上下文的LexicalEnvironment恢复为oldEnv。
  7. 返回? blockValue。

而BlockDeclarationInstantiation中规定:

  1. 令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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 06:30:37