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

时间:2019-07-15 07:29:06

标签: javascript safari v8 javascriptcore javascript-engine

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

我已经简化了代码,使其仅包含一个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问题跟踪器(错误#199823Chromium Discuss和TC39 ECMA262 Github(问题#1632)上打开了该问题。

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

2 个答案:

答案 0 :(得分:0)

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

  

在条件语句中声明函数是非标准的,因此请勿这样做

鉴于它是非标准的,浏览器可以自由地采取不同的行为。

此外,我认为您的技术不会带来任何好处。只需取消“ if undefined”检查,然后无条件定义顶级功能即可。无论如何,它们只会分配一次。

答案 1 :(得分:0)

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

具体来说,该示例包含三个关键方面:在非严格模式代码中,在一个块内声明一个函数,并在该函数之前引用封锁

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

  

“这是我们由于网络浏览器而不得不指定的内容   实施此行为,然后页面开始依赖它,但是我们   不满意。”-附件B 3.3

马虎模式中,JavaScriptCore的行为与正常行为不同:

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

#### jsc
hmm
  

一种解决方案是使用“严格”模式:

λ 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 KirslingKevin Gibbons在GitHub问题#1632上发表的评论。

2016年报告了一个与该起吊行为有关的现有错误,即Webkit Issue #16309: [ES6]. Implement Annex B.3.3 function hoisting rules for global scope。这是一个Test262案例,涵盖了这一点。

  

为解决此问题,我使用了函数表达式

也就是说,我将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上也可以正常运行。关于块作用域和在What are the precise semantics of block-level functions in ES6?上吊起的很好的解释。