【问题标题】:What are the precise semantics of block-level functions in ES6?ES6 中块级函数的精确语义是什么?
【发布时间】:2015-10-03 20:58:03
【问题描述】:

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

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

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

|在块外可见? |吊起?到哪一点? | “TDZ”? | -------------------------------------------------- -------------------------------------------------- -------------------- |非严格模式,没有“网络扩展” | | | | |严格模式,没有“网络扩展” | | | | |非严格模式,带有“网络扩展 | | | | |严格模式,带有“网络扩展” | | | |

我也不清楚在这种情况下“严格模式”是什么意思。 Annex B3.3 中似乎引入了这种区别,作为函数声明的运行时执行的一些额外步骤的一部分:

1. If strict is false, then
...

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

// Non-strict surrounding code

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

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

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

【问题讨论】:

  • 请忘记“提升”这个词。在执行任何代码之前,都会处理所有函数声明。块级作用域影响标识符解析的发生方式(即与“提升”无关),在块内声明的函数可能在块外可用,也可能不可用。哦,函数声明是在变量声明之后处理的,因此它们会覆盖变量(当然,稍后对变量的赋值可以改变这一点……)。

标签: javascript ecmascript-6 language-lawyer


【解决方案1】:

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

没有。是的。它确实指的是函数的严格性 (or script)其中包含函数声明的块出现。不是要声明(或不是)要声明的函数的严格性。

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

请注意,没有网络兼容性语义的严格代码和草率代码之间没有区别。在纯 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
3:ES5.1 §12 中的注释谈到“实现之间存在显着且不可调和的变化”(例如these)。建议使用警告。

那么现在,具有 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) 的时间死区的意义上,不是。但是在body执行中遇到函数声明之前,函数作用域变量是undefined(尤其是在block之前),如果你尝试调用它也会得到一个异常。


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

【讨论】:

  • 优秀的答案!当以下情况发生时会发生什么:(1)函数声明不“理智”?或者(2)函数声明是“顶级块级”(即包含块是顶级而不是嵌套在函数中)?
  • @rvidal:然后应用默认的块级提升/作用域(在这两种情况下)并且该功能在块外部不可见。
  • @rvidal:第一个不会改变我的答案 :-) 第二个确实引用了 ES6 规范中关于仅适用于函数范围的遗留行为的勘误表(它不应该),但无论如何我并没有在我的回答中区分这一点。顺便说一句,我第一段中的第一个链接已经提到了这个……
  • @user51462 "步骤 36 初始化步骤 10 中标识的 FD" - 但这不包括来自内部块的函数声明。它们提升到功能级别由§B.3.3.1 管理
  • @user51462 varDeclarationsdoes not contain non-var declarations from blocks 的列表。如果我理解正确,它甚至是directly goes to TopLevelVarScopedDeclarations only
【解决方案2】:

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

【讨论】:

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