Scoping of function declarations within `with` statements - javascript

Preface: Use of with is discouraged in JavaScript for good reason. It can lead to confusing code, and forward compatibility problems (when new properties are added to built-in objects, for example). This question isn't about whether or not with should be used - it's about the spec-defined behavior of with.
Should the following code work?
let foo = {};
with(foo) {
function bar() {
console.log("hello");
}
}
bar();
It works in Chrome 80, but not in Firefox 72: TypeError: bar is not a function.
Edit: Turns out this error only occurs when pasting into the Firefox console (https://i.imgur.com/WTG3iiX.png), not when running the code within a HTML document.
But notice it's a TypeError, and not a ReferenceError (i.e. bar is not defined). To confirm this we can add console.log("bar" in window) before bar();, and notice that outputs true in Firefox, whereas if you write that before the code it outputs false. So in Firefox the above code has the effect of setting window.bar to undefined.
This works fine in both Firefox and Chrome:
if(true) {
function bar() {
console.log("hello");
}
}
bar();
Just as I'd have expected, since a function foo() {...} declaration is function scoped. So unless there's something weird about with block scopes, it seems like this is a Firefox bug?

I haven't been able to reproduce the problem with Firefox 73, so Firefox's behaviour may have changed.
That said, see MDN on the subject of blocks:
In strict mode, starting with ES2015, functions inside blocks are scoped to that block. Prior to ES2015, block-level functions were forbidden in strict mode.
IIRC, the interaction between hoisting rules and blocks was undefined which resulted in different behaviour in different JS engines. This isn't so much a Firefox bug as a bug in the definition of the language itself.
Avoid function declarations in blocks.

Related

JavaScript function declaration hoisting from within loops [duplicate]

W.r.t Hoisting of fxn definitions.
if (true) {
function foo() {
alert(1)
}
} else {
function foo() {
alert(2)
}
}
foo()
Chrome, some 2-3 months ago - would print 2. Now, it's printing 1. Did I miss something or, did console stop hoisting on fxn's!
DEMO -- prints 1. I'm not sure where to find demo of the older browser version. Probably older v8 engine's node installation?.
Current chrome version - 49
The code you have is invalid in strict mode. Functions don't get hoisted out of blocks (or at least they shouldn't), function declarations inside blocks were completely illegal until ES6. You should write
"use strict";
var foo;
if (true) {
foo = function() {
alert(1)
};
} else {
foo = function() {
alert(2)
};
}
foo()
to get the desired behaviour with reproducible and expected results.
Did I miss something or, did console stop hoisting on fxn's!
Looks like V8 was updated to align with the ES6 spec. It does "hoist" them to the function/top scope, but only when the declaration is actually encountered (in your case, conditionally).
You should avoid using conditionally created functions.
For example, assume the following code:
if (false){
function foo(){
console.log(1)
}
}
foo()
Firefox will not hoist the function and this will result in ReferenceError: foo is not defined. Chrome, however, hoists the function nonetheless and prints 1. So obviously you have deal with different browser behaviour. Therefore, do not do things like that at all (or use function expressions if you really want to).
Also see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function
Functions can be conditionally declared, that is, a function statement can be nested within an if statement. Most browsers other than Mozilla will treat such conditional declarations as an unconditional declaration and create the function whether the condition is true or not, see this article for an overview. Therefore they should not be used, for conditional creation use function expressions.
Especially look at the linked article which somewhat explains the issue you are seeing. So Chrome seems to have changed something in that regard. But again, do not use conditionally created functions.
And note that, as FREEZE commented, you should use 'use strict'; which would not allow such code but throws an exception instead.

Does strict mode prohibit statement level function declarations?

"use strict";
if (true) {
function foo() {
}
}
In PhpStorm this code shows an error:
Function statement not at top level of a program or function is prohibited
However, Chrome happily executes it, even in the debugger and without any console output.
Now is it prohibited or not?
Yes, in ES5 they are prohibited (and in strict mode, all implementations throw). See also Kangax' great article for function statements in sloppy mode.
However, in ES6 they are block-level function declarations with new semantics. See also What are the precise semantics of block-level functions in ES6?. This seems to be what Chrome implements here; foo is not available outside of the if block.

Firefox: function hoisting error

I used to assume that functions always get hoisted to the top of any block of JavaScript code.
For example, this works:
document.addEventListener('something', dummy);
function dummy(){
console.log('dummy');
}
but this doesn't work and throws ReferenceError in Firefox, but works in Chrome:
if(document){
document.addEventListener('something', dummy1);
function dummy1(){
console.log('dummy');
}
}
Fiddle code
Initially, I assumed that Chrome would also throw an error before I tested, but somehow it works correctly. Can someone explain why it doesn't work in Firefox?
It appears this has been an issue for quite a while - here's a reference from 2011: http://statichtml.com/2011/spidermonkey-function-hoisting.html
Apparently Firefox will happily hoist function declarations OUTSIDE of a block, but doesn't do so INSIDE a block. The author of the linked article argues this is (while unexpected) compliant with the ECMA-262 spec, which only allows statements inside of a block...as a function declaration is not a statement. However, I'll note that Firefox happily allows function declarations inside of a block - it merely refuses to hoist them.

Javascript Hoisting in Chrome And Firefox

Running this in Chrome and Firefox gives different answers:
(function() {
if(true) {
function f() { alert("yes"); };
} else {
function f() { alert("no"); };
}
f();
})();
In Chrome the result is 'no'
In Firefox the result is 'yes'
Why the difference?
Declaring functions inside conditional statements is non-standard, so do not do that. That's a known issue. You may use function expressions instead of the declarations:
var f;
if(true) {
f = function() { alert("yes"); };
} else {
f = function() { alert("no"); };
}
f();
The famous Kangax article on function expressions gives some additional details:
FunctionDeclarations are only allowed to appear in Program or FunctionBody. Syntactically, they can not appear in Block ({ ... }) — such as that of if, while or for statements. This is because Blocks can only contain Statements, not SourceElements, which FunctionDeclaration is.
The same article also says:
It's worth mentioning that as per specification, implementations are allowed to introduce syntax extensions (see section 16), yet still be fully conforming. This is exactly what happens in so many clients these days. Some of them interpret function declarations in blocks as any other function declarations — simply hoisting them to the top of the enclosing scope; Others — introduce different semantics and follow slightly more complex rules.
From V8 (Chrome JavaScript engine) bug tracker:
Not a bug. Firefox is the only browser that does what you're expecting.
The behavior of Safari and IE on this is the same as Chrome's/V8's.
This happens due to Firefox lack of function hoisting, as conceived in ECMAScript 5.
Chrome correctly assigns a value to f() before running the body of the function,
so the first version of f() is overwritten by the second one.
SpiderMonkey (Firefox’s JavaScript engine) runs the code without pre-assignin a value to f(),
so it uses the only value that encounters on its way: function f() { alert("yes"); };
what's function hoisting?
JavaScript’s function scope means that all variables declared within a function are visible
throughout the body of the function. Curiously, this means that variables are even
visible before they are declared. This feature of JavaScript is informally known as hoisting:
JavaScript code behaves as if all variable declarations in a function (but not any
associated assignments) are “hoisted” to the top of the function.
sources:
http://statichtml.com/2011/spidermonkey-function-hoisting.html
2011 - o'reilly - javascript - the definitive guide 6th edition

Why are function declarations handled differently in different browsers?

Although I couldn't find a reference to this easily in google, I'm familiar with the fact that, in javascript, global function declarations get interpreted before any code is executed. In other words, this works fine:
f();
function f() {}
However, I've noticed that chrome and firefox have different interpretations of what a global function declaration is. In particular, chrome is happy reading a function declaration that is inside an if block in the first pass, but firefox is not.
try {document.write(f);} // works in chrome
catch(e) {document.write(e.message);} // throws an error in firefox
try {document.write(g);} // works in chrome and firefox
catch(e) {document.write(e.message);}
if(true) function f() {}
function g() {}
You can try this example yourself with this fiddle. I'm using Chrome 16.0.912.75 and Firefox 9.0.1.
What is the ECMA standard for this behavior? Is there a term for this process of "lifting" function declarations above other code? Is what code gets "lifted" open to interpretation (are both browsers right)? Or is it a bug in one of them?
This answer is outdated since the release of ES6 in 2015. See What are the precise semantics of block-level functions in ES6? for how it works since then.
Function declarations are not valid in blocks. You have undefined behaviour which is undefined.
Function declarations at a top level (either global or top level within a function) are hoisted.
Function declarations inside blocks are a syntax error in strict mode
(function () {
"use strict";
if (true) {
function g() { }
}
})();
SyntaxError: In strict mode code, functions can only be declared at top level or immediately within another function.
The ECMA standard for this behavior is to throw a SyntaxError when parsing the script. Unfortunately doing that is not compatible with the web as Raynos says.
See Which JS function-declaration syntax is correct according to the standard? for some extended discussion on the issue.

Categories

Resources