JavaScript - 在 Safari 上执行代码之前可用的对象定义

JavaScript - Object definition available before code execution on Safari

我只需要在页面加载时执行一次的对象和函数包含在对象的 undefined 检查中。在我通常使用的 Windows/Linux 上的 Chrome 上,代码运行完美,即代码只执行一次。但是在 iPad 和 MacBook 上的 Safari 上,未定义的检查不起作用,即根据浏览器,object/function 已经声明,甚至代码执行都没有到达那里!

我简化了我的代码,只包含一个 if 循环来检查嵌套函数是否已经声明。因为它不应该是第一次声明,所以我将 someVariable 包含在 if 中,它永远不应该是 undefined.

运行 Chrome 和 Safari 上的相同功能,看看区别。


if (typeof anObject == 'undefined') {

    function anObject(someParameter = 'someParameter') {
        var someProperty = 'someProperty';

        function someMethod(someParameter) {
            console.log(someParameter);
        }
    }

    console.log('Hi');
    var someVariable = 404;
}

在 Chrome 上,您可以看到 'Hi' 的控制台日志记录以及 someVariable 的 404。但是在 Safari 上,没有控制台日志记录并且 someVariable 未定义。

如果你设置断点来理解正在发生的事情——第一个未定义的检查实际上永远不会起作用。 anObject 甚至在声明之前定义。

我尝试搜索 V8(Chrome JS 引擎)和 JavaScriptCore(Safari 引擎)之间的差异,但没有找到任何可靠的信息。我认为这与执行和功能提升有关。如果有人能向我解释这种执行差异的原因会更好。 iPad 上的行为与 Chrome 上的行为相同!

更新:

  1. 我发现了一个关于不同执行的类似问题。似乎 像这样与功能提升有关但不能 找到任何可靠的来源。 Javascript Hoisting in Chrome And Firefox

  2. 现在看起来其实是吊装行为。这通过使用函数表达式来工作。在这种情况下,只需将 function anObject() 替换为 var anObject = function()。通过这样做,我认为,即使函数在执行前被提升和评估,变量也不会被分配函数引用。

  3. 根据 PhistucK 的推荐,我已经在 WebKit 问题跟踪器(Bug #199823), Chromium Discuss and TC39 ECMA262 Github (Issue #1632)上打开了这个问题。

  4. 这是 2016 年报告的现有 Webkit 错误 - Bug 163209 - [ES6]. Implement Annex B.3.3 function hoisting rules for global scope我现在已经在我的回答中总结了研究

您找到的另一个 SO 问题实际上回答了这个问题。简而言之:

Declaring functions inside conditional statements is non-standard, so do not do that.

鉴于它是 non-standard,浏览器可以自由地有不同的行为。

除此之外,我认为您的技术没有任何好处。只需删除 "if undefined" 检查并无条件地在顶层定义函数。他们只会被分配一次。

此行为与在 Webkit 引擎中使用草率模式有关,which has a bug。让我总结一下研究:

具体而言,该示例具有三个关键方面:在 non-strict 模式 代码中,函数是 在块中声明的 在该块之前引用 .

正如 Annex B.3.3 的介绍所解释的那样,块语句中的函数声明最初并不是语言规范的一部分;这是浏览器经常实现的扩展,每个浏览器都以自己独特的方式实现。 ES2015 试图尽可能多地指定这种行为,但由于浏览器之间的差异并不完全一致,一些现有代码不可避免地仍然不可移植。

"Here are things we were forced to specify because web browsers implemented this behavior and then pages start relying on it, but we aren't happy about it." - Annex B 3.3

草率模式 中,JavaScriptCore 的行为确实与正常行为不同:

λ eshost -sx "if (typeof foo === 'undefined') { function foo() {} print('ok'); } else { print('hmm'); }"
#### ch, sm, v8, xs
ok

#### jsc
hmm

One solution is to use 'strict' mode:

λ eshost -sx "(function () { 'use strict'; if (typeof foo === 'undefined') { function foo() {} print('ok'); } else { print('hmm'); } })()"

#### ch, jsc, sm, v8, xs
ok

此外,这显然只发生在 Safari 的脚本顶层。在函数中,如

function g(){
  console.log(typeof f);
  {
    function f(){}
  }
}

g();

Safari 符合规范。这很可能是因为脚本顶层的行为仅在 ES2016 中指定,在 8582e81 中,而不是函数中的行为,后者在 ES2015 中指定。

来源:Ross Kirsling and Kevin Gibbons on GitHub issue #1632.

发表的评论

2016 年报告了一个与此提升行为相关的现有错误,Webkit 问题 #16309: [ES6]. Implement Annex B.3.3 function hoisting rules for global scope. Here's a Test262 案例涵盖了此问题。

To solve this, I have used Function Expressions:

也就是我把function anObject()换成了var anObject() = function()。 运行这段代码了解现在的流程:

if (typeof anObject == 'undefined') {

  if (typeof anObject == 'undefined') console.log('anObject not defined inside block')
  if (typeof someVariable == 'undefined') console.log('someVariable not defined as of now');

  var anObject = function(someParameter = 'someParameter') {
    var someProperty = 'someProperty';
  }

  console.log('anObject is now defined');
  var someVariable = 404;

  if (typeof someVariable == 'undefined') console.log('someVariable not defined as of now');

}

这里发生了什么?

函数和变量被提升到顶层。但是像 V8 (Chrome) 这样的引擎,在代码执行期间从语义上定义了函数名称。但是,在 Webkit 浏览器上的草率模式下,即使在 ECMA2015/16 标准化之后,函数名称也会在执行之前定义。请注意,在两个引擎上,函数实际上是在任何事情之前定义(提升)的——这只是关于函数 name 的语义。上面的代码在执行期间将匿名函数的引用(因为它现在没有名称)分配给 anObject,这在 Safari 上也将 运行 正常。关于 .

上块范围和提升的一个很好的解释