Objects and functions which I require only to execute once on a page load are wrapped inside an undefined check for the object. On Chrome on Windows/Linux which I usually use, the code works perfectly i.e. code only executes once. But on Safari on both iPad and MacBook, the undefined check doesn't work i.e. as per the browser, the object/function is already declared without even the code execution reaching there!
I've simplified my code to only include an if loop that checks if the nested function is already declared. Since it should not have been declared the first time, I've included someVariable inside the if that should never be undefined.
Run the same function on Chrome and Safari and see the difference.
if (typeof anObject == 'undefined') {
function anObject(someParameter = 'someParameter') {
var someProperty = 'someProperty';
function someMethod(someParameter) {
console.log(someParameter);
}
}
console.log('Hi');
var someVariable = 404;
}
On Chrome, you can see Console logging of 'Hi' as well someVariable as 404. But on Safari, there's no console logging and someVariable goes undefined.
If you put breakpoints to understand what's happening - the first undefined check never works actually. The anObject is defined even before the declaration.
I have tried searching about the differences regarding this between V8 (Chrome JS engine) and JavaScriptCore (Safari's engine) but haven't found anything solid. I assume this is something related to execution and function hoisting. It would be better if someone could explain to me the reason for this difference in execution. The behavior is the same on iPad that too even on Chrome!
Updates:
I've found a similar problem regarding different execution. Seems like this is something related to function hoisting but couldn't find any solid source yet. Javascript Hoisting in Chrome And Firefox. Looks like this behaviour should have been fixed with ECMA2015.
Looks like it is actually a hoisting behaviour now. This works by using function expression. In this case, simple replacing function anObject() with var anObject = function(). By doing this, I think, the variable doesn't get assigned the function reference even if the function is hoisted and evaluated before execution.
Chrome: 75.0.3770.100 Safari: Version 12.1.1 iPad Chrome: 75, iOS 12.3
Originally posted on StackOverflow. I was asked to raise this bug on Engine trackers and TC39.
The answer to your question lies in Annex B.3.3, the darkest corner of the ECMAScript specification. :ghost:
Specifically, there are three key aspects to your 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.
And so, while it certainly does look like a JSC bug when doing a cross-engine comparison...
位 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
...if I'm understanding the spec correctly, combining the three aspects mentioned above ought to result in said unportable code.
(_Even if I am right though, it still makes me wonder if it couldn't be specified and treated as a JSC bug鈥攁ssuming there aren't some legacy Safari-only sites that would pose a web compatibility risk..._ 馃)
You've run into one of the thorniest parts of the JavaScript spec, Annex B.3.3. A simpler reproduction is
console.log(typeof f);
{
function f(){}
}
which in Chrome (and Firefox and ChakraCore) logs 'undefined' and in Safari logs 'function'.
Chrome's behavior is correct per the spec, so you may wish to open a bug with Safari. (In the mean time, though, you should be using strict mode, which has more sensible semantics (and all browsers agree in this case in strict mode).)
Edit: interestingly, 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 8582e812, as opposed to the behavior in functions, which was specified in ES2015.
The answer to your question lies in Annex B.3.3
Thanks for the source. That's exactly what I've been looking for. Also, why did we use a self-invoking function for the 'strict mode' case. I assume it should be working as expected without it, right?
which in Chrome (and Firefox and ChakraCore) logs 'undefined' and in Safari logs 'function'
Actually, you have simplified my code. Interestingly, this should log 'function' in Chrome and 'undefined' on Safari:
if(typeof f == 'undefined');
{
function f(){}
}
console.log(typeof f)
Replacing with this now works on both browsers:
if(typeof f == 'undefined');
{
var f = function(){}
}
console.log(typeof f)
Actually, that's what I did - replaced the code with function expression. I think it should work independently over browsers as the specifications are same regarding block scoped variable and execution regarding function references (confirmed working on Safari and Chrome on different devices).
For the uninformed, function expression works because of var scoping - the function is anonymous here and its reference gets assigned to a variable inside the block execution only. This can be an alternative to using strict mode.
Chrome's behavior is correct per the spec, so you may wish to open a bug with Safari.
Yes, Chrome behaviour does look nearer to standard. I read somewhere that the specifications don't recommend declaration of functions inside conditional blocks. I have opened the bug on Webkit tracker here.
Please tell if the issue needs to closed here. I had thought with ECMA2015, this behaviour should have been standardized. No issues if it appears to be totally dependant on browser implementation now. Thank you for participating - means a lot given that this was my first issue on this thread. :)

I read somewhere that the specifications don't recommend declaration of functions inside conditional blocks.
The behavior you're seeing depends on Annex B, which is the "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" part of the spec, so it's discouraged in that sense. But really the spec is intended to define the semantics of the language, not make recommendations about which parts users should use.
In strict mode functions in blocks work in a reasonable way and Annex B doesn't get involved.
Please tell if the issue needs to closed here. I had thought with ECMA2015, this behaviour should have been standardized.
It is standardized (well, in ES2016), so I don't think there's an issue with the spec: this is just a bug in Safari. Thanks for filing, though!
Sure enough, looks like there's a Test262 case that covers this and an existing WebKit bug.
Never a dull moment with Annex B. 馃槄
Moral of the story: Avoid sloppy mode whenever possible.
Most helpful comment
You've run into one of the thorniest parts of the JavaScript spec, Annex B.3.3. A simpler reproduction is
which in Chrome (and Firefox and ChakraCore) logs
'undefined'and in Safari logs'function'.Chrome's behavior is correct per the spec, so you may wish to open a bug with Safari. (In the mean time, though, you should be using strict mode, which has more sensible semantics (and all browsers agree in this case in strict mode).)
Edit: interestingly, this apparently only happens in Safari at the top level of scripts. In functions, as in
Safari conforms to the spec. This might well be because the behavior at the top level of scripts was only specified in ES2016, in 8582e812, as opposed to the behavior in functions, which was specified in ES2015.