JavaScript - Object definition available before code execution on Safari

孤街浪徒 提交于 2019-12-07 04:55:26
Paras Lehana

This behaviour relates to using sloppy mode in Webkit engines, which has a bug. Let me wrap up the research:

Specifically, there are three key aspects to the example: in non-strict mode code, a function is declared within a block and referenced prior to that block.

As the introduction to Annex B.3.3 explains, function declarations inside block statements were not originally part of the language spec; this was an extension that browsers often implemented, each in their own unique way. ES2015 sought to specify as much of this behavior as possible, but as the differences between browsers were not fully reconcilable, some existing code remained inevitably unportable.

"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

In sloppy mode, JavaScriptCore does behave differently than the normal behaviour:

λ 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

Also, this apparently only happens in Safari at the top level of scripts. In functions, as in

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

g();

Safari conforms to the spec. This might well be because the behavior at the top level of scripts was only specified in ES2016, in 8582e81, as opposed to the behavior in functions, which was specified in ES2015.

Source: Comments posted by Ross Kirsling and Kevin Gibbons on GitHub issue #1632.

There has been an existing bug reported in 2016 related to this hoisting behaviour, Webkit Issue #16309: [ES6]. Implement Annex B.3.3 function hoisting rules for global scope. Here's a Test262 case that covers this.

To solve this, I have used Function Expressions:

That is I replaced function anObject() with var anObject() = function(). Run this code to understand the flow now:

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');

}

What's happening here?

The functions and variables are hoisted to the top level. But engines like V8 (Chrome), semantically defines the function name during code execution. However, in sloppy mode on Webkit browsers, even after the ECMA2015/16 standardization, function name is defined before the execution. Please note that on both engines, the function is actually defined (hoisted) before anything - this is just about the semantics regarding the function name. The code above assigns the anonymous function's reference (because it has no name now) to anObject during the execution and this will run fine on Safari as well. A good explanation about block scopes and hoisting on What are the precise semantics of block-level functions in ES6?.

The other SO question you found actually answers this. In short:

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

Given that it's non-standard, browsers are free to have different behavior.

That aside, I don't think your technique gives any benefit. Just remove the "if undefined" check and define the functions in the top level unconditionally. They will only be assigned once anyway.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!