ES6 中块级函数的精确语义是什么?

IT技术 javascript ecmascript-6 language-lawyer
2021-01-18 08:51:49

我试图通过阅读原始规范来了解 ES6 中新的标准化块级函数。我的粗浅理解是:

  • ES6 中允许块级函数声明。
  • 他们吊到块的顶部。
  • 在严格模式下,它们在包含块之外不可见。

但是,由于这些语义的一部分被指定为“可选”并且仅对 Web 浏览器是必需的(附件 B,这一事实使情况变得更加复杂所以我想填写下表:

                                             | 在块外可见?| 吊起?到什么程度?| “TDZ”?|
-------------------------------------------------- -------------------------------------------------- --------------------
| 非严格模式,没有“网络扩展” | | | |
| 严格模式,没有“网络扩展”| | | |
| 非严格模式,带有“网络扩展 | | | |
| 严格模式,带有“网络扩展” | | | |

我也不清楚“严格模式”在这种情况下的含义。这种区别似乎在附件 B3.3 中引入,作为函数声明的运行时执行的一些附加步骤的一部分:

1. If strict is false, then
...

但是,据我所知,strict[[Strict]]的是函数对象内部槽。这是否意味着:

// Non-strict surrounding code

{
    function foo() {"use strict";}
}

应该被认为是上表中的“严格模式”吗?然而,这与我最初的直觉相矛盾。

请记住,我最感兴趣的是 ES6 规范本身,而不管实际的实现不一致。

2个回答

据我所知,strict[[Strict]]的是函数对象内部槽。

不,是的。它指的是函数(的严格性或脚本,其中包含该函数声明的块发生。与要(或不)声明的函数的严格性无关。

“网络扩展”仅适用于草率(非严格)代码,并且仅当函数语句的外观是“健全的”时 - 即,例如,如果其名称与形式参数或词法不冲突声明的变量。

请注意,没有 Web 兼容性语义的严格代码和草率代码之间没有区别。在纯 ES6 中,块中的函数声明只有一种行为。

所以我们基本上有

                 |      web-compat               pure
-----------------+---------------------------------------------
strict mode ES6  |  block hoisting            block hoisting
sloppy mode ES6  |  it's complicated ¹        block hoisting
strict mode ES5  |  undefined behavior ²      SyntaxError
sloppy mode ES5  |  undefined behavior ³      SyntaxError

1:见下文。要求警告。
2:通常,SyntaxError抛出 a
3:ES5.1 §12 中的注释谈到“实现之间的重大和不可调和的变化”(例如这些)。建议警告。

那么现在,对于具有遗留语义的 sloppy-mode 函数中的块中的函数声明,具有 Web 兼容性的 ES6 实现如何表现?
首先,纯语义仍然适用也就是说,函数声明被提升到词法块的顶部。
然而,还有一个var声明被提升到封闭函数的顶部。
并且当函数声明被评估时(在块中,就好像它像一条语句一样被满足),函数对象被分配给那个函数范围的变量。

这可以通过代码更好地解释:

function enclosing(…) {
    …
    {
         …
         function compat(…) { … }
         …
    }
    …
}

工作原理与

function enclosing(…) {
    var compat₀ = undefined; // function-scoped
    …
    {
         let compat₁ = function compat(…) { … }; // block-scoped
         …
         compat₀ = compat₁;
         …
    }
    …
}

是的,这有点令人困惑,有两个不同的绑定(用下标 0 和 1 表示)具有相同的名称。所以现在我可以简洁地回答你的问题:

在块外可见?

是的,就像一个var. 但是,还有一个仅在块内可见的第二个绑定。

吊起?

是的 - 两次。

到什么程度?

函数(但用 初始化undefined)和块(用函数对象初始化)。

“TDZ”?

不是在词法声明的变量(的时间死区的意义let/ const/ class)上引用,没有罚球。但是在函数体的执行中遇到函数声明之前,函数范围的变量是undefined(特别是在块之前),如果你尝试调用它,你也会得到一个异常。


仅供参考:在 ES6 中,上述行为仅针对函数作用域中的块指定。由于 ES7这同样适用于块eval和全局作用域。

@rvidal:然后应用默认的块级提升/作用域(在这两种情况下)并且该函数在块的外部不可见。
2021-03-10 08:51:49
@user51462 该列表varDeclarations 不包含var来自 blocks 的声明如果我理解正确,它甚至直接转到TopLevelVarScopedDeclarations
2021-03-24 08:51:49
@rvidal:第一个不会改变我的答案:-) 第二个确实引用了 ES6 规范中关于这个遗留行为的勘误表,该行为仅适用于函数作用域(它不应该),但我没有在反正我的回答。顺便说一句,我第一段中的第一个链接已经提到了这一点……
2021-04-01 08:51:49
优秀的答案!如果出现以下情况会发生什么: (1) 函数声明不是“理智的”?或者 (2) 函数声明是“顶级块级”(即包含块是顶级的而不是嵌套在函数中)?
2021-04-06 08:51:49
@ user51462“第 36 步初始化第 10 步中标识的FD” ——但这不包括来自内部块的函数声明。将它们提升到功能级别由§B.3.3.1 管理
2021-04-07 08:51:49

我不确定你的困惑来自哪里。根据10.2.1很清楚什么是“严格模式”,什么不是。在您的示例中,foos[[Strict]]内部插槽true确实并且将处于严格模式,但托管它的块不会。第一句话(您引用的那句话)与托管块有关,而不是其中生成的内容。您片段中的块不是严格模式,因此该部分适用于它。

下面是一组偏离标准的条件和行为,并定义了替代行为,其目的是允许所述遗留代码按照最初设计的方式运行。如果您查看 9.2.12 的第 29 步,您会看到此步骤在非严格模式下的详细信息可追溯到 B3.3。如果您的原始块处于严格模式(例如,如果它以“use strict”开头),则会跳过整个步骤并应用正常的标准行为。我希望这更清楚一点,因为如果没有,我想我不能给你一个满意的答案(对不起:-)
2021-03-19 08:51:49
@rvidal 我真的不明白你的逻辑。考虑一下{ var f, isStrict = IsInStrict(); if(!isStrict) { f = function() {'use strict'} } }现在,假设IsInStrict“有效”(有一些建议如何在 SO 上进行测试),如果不是在严格模式下,通过进入 if 语句的逻辑会将外部范围变成严格模式。这不合逻辑,实际上造成了一个悖论。
2021-03-24 08:51:49
@rvidal - 让我们再试一次:)。附录 B 是关于不属于标准的遗留功能。列出它们是为了描述非标准的行为,但为了保持与旧代码的兼容性,它们是必需的。B3.3 描述了一个这样的特性,即“块级函数声明”。附加步骤是在 9.2.12 的步骤 29 中要考虑的步骤,并以相关块不处于严格模式的条件开始。在您的示例中,这确实与在块内声明的函数有关,该块确实是非严格的(由于没有明确地严格)(续下)
2021-04-06 08:51:49
@rvidal - 让我们尝试一条示范路径:-)。看看这个小提琴这是否让您更加/不那么困惑?
2021-04-06 08:51:49
你确定吗?我认为B3.3 中的strict指的是先前在9.2.12的算法描述中引入的局部变量在该算法中,strict == func.[[Strict]].
2021-04-09 08:51:49