Safari 的这种行为是否违反了 ECMAScript 规范?
Does this Safari behavior break the ECMAScript specification?
以下代码在 OSX 上的 Safari 13.0.4 中打印 1
。
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
此行为是否与规范的 section 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).
据我了解,函数声明 放在 block, should follow the specification of 13.2.14 中的代码(我用粗体显示):
When a Block or CaseBlock is evaluated a new declarative Environment Record is created and bindings for each block scoped variable, constant, function, or class declared in the block are instantiated in the Environment Record.
其中一个步骤显式处理函数声明,这取决于 InstantiateFunctionObject, which in turn depends on OrdinaryFunctionCreate, OrdinaryObjectCreate, MakeBasicObject ... 创建一个 new 对象。
所有这一切都发生在评估。您对规范的引用表明每次迭代都会进行评估,因此应该在每次迭代中新创建函数对象。
实现上的差异
规范中有一节介绍了与 block-level function declarations 相关的实现差异。它说:
Prior to ECMAScript 2015, the ECMAScript specification did not define the occurrence of a FunctionDeclaration as an element of a Block statement's StatementList. However, support for that form of FunctionDeclaration was an allowable extension and most browser-hosted ECMAScript implementations permitted them. Unfortunately, the semantics of such declarations differ among those implementations. Because of these semantic differences, existing web ECMAScript code that uses Block level function declarations is only portable among browser implementation if the usage only depends upon the semantic intersection of all of the browser implementations for such declarations. The following are the use cases that fall within that intersection semantics:
A function is declared and only referenced within a single block
- One or more FunctionDeclarations whose BindingIdentifier is the name f occur within the function code of an enclosing function g and that declaration is nested within a Block.
- No other declaration of f that is not a
var
declaration occurs within the function code of g
- All occurrences of f as an IdentifierReference are within the StatementList of the Block containing the declaration of f.
现在您问题中的情况根据规范(打印 2)当代码不是顶级脚本,而是放在函数体中时。在那种情况下,我们处于情况 1(在上面的引用中)。但是当脚本是全局的时候这一点就不适用了。因此,我们确实看到了偏差行为...
是的,这是 Safari 中的一个错误[1]。但是,正如您所注意到的,它仅在 全局(或 eval
)范围内 和 仅在草率模式 .[=14= 中出现]
一般来说,这些绝对应该是不同的函数实例,而不是被提升到块之外。但是,作为浏览器的 Safari 确实实现了规范附件 B3.3 中的 块级函数声明 Web 遗留兼容性语义 (参见 for details). In ES6 and ES7, these did apply only to block statements inside functions though. Only since ES8,它们也被指定用于全局和 eval 范围内的声明实例化。
Safari 似乎还没有采用 ES8 的更改,并且在全局范围内保留了自己的(不兼容的)pre-ES6 semantics 块范围声明,在那里他们完全提升了声明。
1:可能#201695 or #179698。 “我们不在全局范围内支持它。我们在函数内部支持它,我相信 eval。我们仍然需要在全局范围内实现它。”
以下代码在 OSX 上的 Safari 13.0.4 中打印 1
。
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
此行为是否与规范的 section 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).
据我了解,函数声明 放在 block, should follow the specification of 13.2.14 中的代码(我用粗体显示):
When a Block or CaseBlock is evaluated a new declarative Environment Record is created and bindings for each block scoped variable, constant, function, or class declared in the block are instantiated in the Environment Record.
其中一个步骤显式处理函数声明,这取决于 InstantiateFunctionObject, which in turn depends on OrdinaryFunctionCreate, OrdinaryObjectCreate, MakeBasicObject ... 创建一个 new 对象。
所有这一切都发生在评估。您对规范的引用表明每次迭代都会进行评估,因此应该在每次迭代中新创建函数对象。
实现上的差异
规范中有一节介绍了与 block-level function declarations 相关的实现差异。它说:
Prior to ECMAScript 2015, the ECMAScript specification did not define the occurrence of a FunctionDeclaration as an element of a Block statement's StatementList. However, support for that form of FunctionDeclaration was an allowable extension and most browser-hosted ECMAScript implementations permitted them. Unfortunately, the semantics of such declarations differ among those implementations. Because of these semantic differences, existing web ECMAScript code that uses Block level function declarations is only portable among browser implementation if the usage only depends upon the semantic intersection of all of the browser implementations for such declarations. The following are the use cases that fall within that intersection semantics:
A function is declared and only referenced within a single block
- One or more FunctionDeclarations whose BindingIdentifier is the name f occur within the function code of an enclosing function g and that declaration is nested within a Block.
- No other declaration of f that is not a
var
declaration occurs within the function code of g- All occurrences of f as an IdentifierReference are within the StatementList of the Block containing the declaration of f.
现在您问题中的情况根据规范(打印 2)当代码不是顶级脚本,而是放在函数体中时。在那种情况下,我们处于情况 1(在上面的引用中)。但是当脚本是全局的时候这一点就不适用了。因此,我们确实看到了偏差行为...
是的,这是 Safari 中的一个错误[1]。但是,正如您所注意到的,它仅在 全局(或 eval
)范围内 和 仅在草率模式 .[=14= 中出现]
一般来说,这些绝对应该是不同的函数实例,而不是被提升到块之外。但是,作为浏览器的 Safari 确实实现了规范附件 B3.3 中的 块级函数声明 Web 遗留兼容性语义 (参见
Safari 似乎还没有采用 ES8 的更改,并且在全局范围内保留了自己的(不兼容的)pre-ES6 semantics 块范围声明,在那里他们完全提升了声明。
1:可能#201695 or #179698。 “我们不在全局范围内支持它。我们在函数内部支持它,我相信 eval。我们仍然需要在全局范围内实现它。”