首页 > 解决方案 > 这种 Safari 行为是否违反了 ECMAScript 规范?

问题描述

以下代码1在 OSX 上的 Safari 13.0.4 中打印。

let set = new Set

for(let x = 0; x < 2; x++) {
    function f() {}
    set.add(f)
}

console.log(set.size) // 1 in Safari non-strict mode

还:

let set = new Set

for(let x = 0; x < 2; x++) {
    function f() {}
    f.test = x
    set.add(f)
}

console.log(set.size); // 1 in Safari
for(let x of set) console.log(x.test) // 1 in Safari non-strict mode

和:

let set = new Set;

for(let x = 0; x < 2; x++) {
    var v = (function () {})
    set.add(v);
}

console.log(set.size); // 2 in Safari non-strict mode

此行为是否与规范的第 13.7.4.8 节(见下文)兼容?

请注意:Node 13.9.0、Chrome 80.0.3987.122 和 Brave 1.3.118 打印2

规范的 13.7.4.8:

(4.b 似乎相关)

The abstract operation ForBodyEvaluation with arguments test, 
increment, stmt, perIterationBindings, and labelSet is 
performed as follows:

1. Let V = undefined.

2. Let status be CreatePerIterationEnvironment(perIterationBindings).

3. ReturnIfAbrupt(status).

4. Repeat

  a. If test is not [empty], then

    i. Let testRef be the result of evaluating test.

    ii. Let testValue be GetValue(testRef).

    iii. ReturnIfAbrupt(testValue).

    iv. If ToBoolean(testValue) is false, return NormalCompletion(V).

  b. Let result be the result of evaluating stmt.

  c. If LoopContinues(result, labelSet) is false, return d.
     Completion(UpdateEmpty(result, V)).

  d. If result.[[value]] is not empty, let V = result.[[value]].

  e. Let status be CreatePerIterationEnvironment(perIterationBindings).

  f. ReturnIfAbrupt(status).

  g. If increment is not [empty], then

    i. Let incRef be the result of evaluating increment.

    ii. Let incValue be GetValue(incRef).

    iii. ReturnIfAbrupt(incValue).

标签: javascriptmacossafari

解决方案


据我了解,在block中放置函数声明的代码应遵循13.2.14的规范(我用粗体表示):

当评估案例块时,会创建一个的声明性环境记录,并在环境记录中实例化块中声明的每个块范围变量、常量、函数或类的绑定。

其中一个步骤显式地处理函数声明,这取决于InstantiateFunctionObject,而后者又取决于创建对象的OrdinaryFunctionCreateOrdinaryObjectCreateMakeBasicObject ...。

这一切都发生在评估中。您对规范的引用表明每次迭代都会进行评估,因此应该在每次迭代中新创建函数对象。

实施上的差异

该规范有一节介绍与块级函数声明相关的实现差异。它说:

在 ECMAScript 2015 之前,ECMAScript 规范没有将FunctionDeclaration的出现定义为Block语句的StatementList的元素。但是,对这种形式的FunctionDeclaration的支持是允许的扩展,并且大多数浏览器托管的 ECMAScript 实现都允许它们。不幸的是,这些声明的语义在这些实现中有所不同。由于这些语义差异,使用Block的现有 Web ECMAScript 代码如果使用仅取决于此类声明的所有浏览器实现的语义交集,则级别函数声明仅可在浏览器实现之间移植。以下是属于该交集语义的用例:

  1. 一个函数被声明并且只在一个块中被引用

    • 一个或多个BindingIdentifier为名称fFunctionDeclarations出现在封闭函数g的函数代码中,并且该声明嵌套在Block中。
    • 在g的函数代码中不会出现不是声明的f的其他声明var
    • 所有作为IdentifierReference出现的 f都在包含f声明的Block的StatementList中。

现在,当代码不是顶级脚本而是放置在函数体中时,您问题中的案例的行为符合规范(打印 2) 。在这种情况下,我们处于情况 1(在上面的引用中)。但是当脚本是全局的时,这一点不适用。因此,我们确实看到了偏差行为......


推荐阅读