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

34

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

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

然而,这个问题变得更加复杂的原因是,其中部分语义被规定为“可选”,仅对Web浏览器强制执行(附录B)。因此,我想要填写以下表格:

                                             |  块外是否可见?  |  提升? 提升的范围是什么?  |   "TDZ"? |
------------------------------------------------------------------------------------------------------------------------
|   非严格模式,无“web扩展”         |                             |                               |          |
|   严格模式,无“web扩展”             |                             |                               |          |
|   非严格模式,带有“web扩展”   |                             |                               |          |
|   严格模式,带有“web扩展”         |                             |                               |          |

此外,我没有明确了解到在这个上下文中“严格模式”的含义。这个区别似乎是在附录B3.3中引入的,作为一些函数声明运行时执行的额外步骤的一部分:

1. If strict is false, then
...

然而,就我所见,strict 指的是函数对象的 [[Strict]] 内部插槽。这是否意味着:

// Non-strict surrounding code

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

在上表中是否应该考虑为“严格模式”?然而,这与我的初始直觉相矛盾。

请记住,我主要关注ES6规范本身,而不考虑实际实现的不一致性。


3
请忘记术语“提升”。所有函数声明在执行任何代码之前都会被处理。块级作用域影响标识符解析的方式(即与“提升”无关),在块内声明的函数可能可用于块外,也可能不可用。哦,还有函数声明在变量声明之后处理,因此它们会覆盖变量(当然,对变量的后续赋值可以改变这一点...)。 - RobG
相关 - Ben Aston
2个回答

50
据我所见,strict指的是函数对象的[[Strict]]内部槽位。不完全正确,但也不完全错误。它确实指的是包含函数声明块的函数(或脚本)的严格性,而不是将要(或不将要)声明的函数的严格性。
"Web扩展"仅适用于松散(非严格)代码,并且仅在函数语句的外观“合理”时才适用——例如,如果其名称与正式参数或词法声明的变量不冲突。
请注意,没有 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
3: ES5.1 §12中的注释谈到了“实现之间存在显著且不可调和的差异”(例如这些)。建议发出警告。

那么,一个具有 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和全局作用域中的块。


1
@rvidal:然后默认的块级提升/作用域适用于两种情况,函数在块的外部是不可见的。 - Bergi
以下是与您的答案相关的一些链接,可能需要更新: https://github.com/estools/escope/issues/73 https://esdiscuss.org/topic/block-level-function-declarations-web-legacy-compatibility-bug - rvidal
1
@rvidal:第一个并不改变我的答案 :-) 第二个确实涉及到ES6规范中有关这种遗留行为仅适用于函数作用域的勘误(其实不应该),但我在回答中没有区分。顺便说一下,我第一段中的第一个链接已经提到了这一点... - Bergi
1
@user51462 "步骤36初始化步骤10中识别的FDs" - 但这不包括来自内部块的函数声明。它们提升到函数级别是由§B.3.3.1控制的。 - Bergi
1
@user51462,varDeclarations列表不包含来自块的非var声明。如果我理解正确,它甚至直接进入TopLevelVarScopedDeclarations - Bergi
显示剩余9条评论

3
我不确定你的困惑来源。 根据10.2.1,"严格模式"中有什么或没有什么非常清楚。 在你的示例中,foo[[Strict]]内部插槽确实是true并且将在严格模式下运行,但是托管它的块不会。 第一句话(你引用的那句)与托管块有关,而不是其中生成的内容。 你片段中的块不在严格模式下,因此该部分适用于它。

你确定吗?我认为B3.3中的“strict”是指先前在9.2.12算法描述中引入的局部变量。而在该算法中,strict == func.[[Strict]] - rvidal
2
@rvidal,我真的不理解你的逻辑。考虑这个 { var f, isStrict = IsInStrict(); if(!isStrict) { f = function() {'use strict'} } }。现在,假设 IsInStrict "有效"(有一些建议在 SO 上如何测试它),如果不在严格模式下,按照你的逻辑进入 if 语句会将外部作用域转换为严格模式。这是不合逻辑的,实际上会产生悖论。 - Amit
2
@rvidal - 让我们再试一次 :). 附录B是关于遗留功能的,它们不是标准的一部分。它们被列出来描述非标准的行为,但是为了保持与旧代码的兼容性而需要。B3.3描述了这样一个特性,即“块级函数声明”。附加步骤是在9.2.12的第29步考虑的,并且从一个条件开始,即相关块不处于严格模式下。在您的示例中,这确实涉及在块内声明的函数,该块确实是非严格的(因为没有明确指定为严格)(续下) - Amit
2
以下是一组条件和行为,它们偏离了标准并定义了一种替代行为,其目的是允许所述遗留代码按照最初设计的方式运行。如果您查看9.2.12步骤29,您会发现该步骤引用B3.3以获取非严格模式下的详细信息。如果您的原始块处于严格模式(例如,如果它以'use strict'开头),则将跳过整个步骤,并应用正常的标准行为。希望这更加清晰,因为如果不是这样,我认为我将无法给您一个令人满意的答案(那就很抱歉了 :-) - Amit
1
@rvidal - 让我们尝试一个演示路径 :-). 看看这个fiddle。这会让你更加困惑还是更少困惑? - Amit
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接