"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.
Related
Why the following codes output different results between Chrome and Firefox?
f = function() {return true;};
g = function() {return false;};
(function() {
if (g() && [] == ![]) {
f = function f() {return false;};
function g() {return true;}
}
})();
console.log(f());
In Chrome: the result is false. However, in Firefox, it is true.
The key line of the above codes is line 4, and base on my knowledge of function name hoisting, the function g should be in line 6, namely the line 2 is overridden by line 6. IMO, the behavior of Chrome is correct.
Am I right on this? if so, why Firefox outputs different results?
ECMAScript 5, the current official specification of the JavaScript language, does not define the behavior for function declarations inside blocks.
Quoting Kangax:
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. If we look at production rules carefully, we can see that the only way Expression is allowed directly within Block is when it is part of ExpressionStatement. However, ExpressionStatement is explicitly defined to not begin with "function" keyword, and this is exactly why FunctionDeclaration cannot appear directly within a Statement or Block (note that Block is merely a list of Statements).
Because of these restrictions, whenever function appears directly in a block (such as in the previous example) it should actually be considered a syntax error, not function declaration or expression. The problem is that almost none of the implementations I've seen parse these functions strictly per rules (exceptions are BESEN and DMDScript). They interpret them in proprietary ways instead.
Also worth quoting the ECMAScript 6 draft - B.3.3 Block-Level Function Declarations Web Legacy Compatibility Semantics:
Prior to the Sixth Edition, the ECMAScript specification did not define the occurrence of a FunctionDeclaration as an element of a Block statement’s StatementList. However, support for that form of FunctionDeclaration was an allowable extension and most browser-hosted ECMAScript implementations permitted them. Unfortunately, the semantics of such declarations differ among those implementations. [...]
As ES5 does not define the behavior for function declarations inside blocks while allowing proprietary extensions, there are technically no "rights" or "wrongs". Consider them "unspecified behavior" which is not portable across different ES5-compliant environments.
Those are easy to rewrite into portable code anyway:
Should the function declaration be hoisted to the top of the current function/global scope? Make sure the function declaration is not directly inside of a block.
Should the function be declared only when the block executes? Assign a function expression to a variable (var f = function() {};). Note that there is no hoisting and the variable is still accessible outside of the block (var declarations are function-level scoped).
As per ECMAScript 6, function declarations are block-scoped, so Firefox implements the correct behavior ES6-wise.
We have something like this
if(true) {
const a = 1;
function myFunc() {
alert(a);
}
myFunc();
}
In Safari 11 this cause "ReferenceError: Can't find variable: a".
The same code works without error in Chrome and Firefox.
Using "strict mode" in Safari solve the issue.
I think that the main problem is the different scope of const a and function myFunc. The last one, in fact, is a global function due the fact that the conditional statement does not create a block scope for functions inside it (I suppose for legacy reasons) as it does for let and const.
I'm wondering if Safari has right in this case because we're mixing things with different scope.
Is there some official resource that explains this case? I don't find any mention of this behaviour either in caniuse and mdn sites
Function declarations inside blocks weren't defined in the specification for many years but they were allowed by different javascript engines.
Since this syntax was not defined in the specification and was allowed by the javascript engines, different engines did different things. Some made it a syntax error, others treated function declarations in block scopes as they were function expressions. Some engines treated functions declarations in a block scope like multiple hoisted declarations in the same scope.
As of ES2015, function declarations are part of the specification and there are two ways they are handled:
Standard web semantics
Legacy web semantics
Standard Semantics
With standard semantics, function declarations are converted to function expressions, declared with let keyword and are hoisted at the top of the block. Standard semantics are in effect in strict mode.
So in strict mode, your code will be treated by the javascript engine as though it were written like this:
if(true) {
let myFunc = function() {
alert(a);
}
const a = 1;
myFunc();
}
Legacy Web Semantics
In non-strict mode on browsers, legacy web semantics apply. When function declarations in block scope are not treated as syntax errors, there are three scenarios that are handled the same way by all major javascript engines. Those three scenarios are:
Function is declared and referenced within a single block
A function is declared and possibly used within a single Block but also referenced by an inner function definition that is not contained within that same Block.
A function is declared and possibly used within a single block but also referenced within
subsequent blocks.
In addition to let variable for the function defined in block scope, there's also a variable defined with var in the containing function scope or the global scope. This var assignment isn't hoisted to the top of the block and is done when the function declaration is reached in the code.
Your code in non-strict mode is treated by javascript engine as:
var varMyFunc;
if(true) {
let myFunc = function() {
alert(a);
}
const a = 1;
varMyFunc = myFunc; // at the place of function declaration
myFunc();
}
You shouldn't write code that relies on legacy web semantics. Instead, use strict mode to ensure that your code relies on standard rules for handling function declarations in block scopes. Having said all that, if you have legacy code in non-strict mode that relies on legacy web semantics, you can expect it to work cross-browser.
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.
Why the following codes output different results between Chrome and Firefox?
f = function() {return true;};
g = function() {return false;};
(function() {
if (g() && [] == ![]) {
f = function f() {return false;};
function g() {return true;}
}
})();
console.log(f());
In Chrome: the result is false. However, in Firefox, it is true.
The key line of the above codes is line 4, and base on my knowledge of function name hoisting, the function g should be in line 6, namely the line 2 is overridden by line 6. IMO, the behavior of Chrome is correct.
Am I right on this? if so, why Firefox outputs different results?
ECMAScript 5, the current official specification of the JavaScript language, does not define the behavior for function declarations inside blocks.
Quoting Kangax:
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. If we look at production rules carefully, we can see that the only way Expression is allowed directly within Block is when it is part of ExpressionStatement. However, ExpressionStatement is explicitly defined to not begin with "function" keyword, and this is exactly why FunctionDeclaration cannot appear directly within a Statement or Block (note that Block is merely a list of Statements).
Because of these restrictions, whenever function appears directly in a block (such as in the previous example) it should actually be considered a syntax error, not function declaration or expression. The problem is that almost none of the implementations I've seen parse these functions strictly per rules (exceptions are BESEN and DMDScript). They interpret them in proprietary ways instead.
Also worth quoting the ECMAScript 6 draft - B.3.3 Block-Level Function Declarations Web Legacy Compatibility Semantics:
Prior to the Sixth Edition, the ECMAScript specification did not define the occurrence of a FunctionDeclaration as an element of a Block statement’s StatementList. However, support for that form of FunctionDeclaration was an allowable extension and most browser-hosted ECMAScript implementations permitted them. Unfortunately, the semantics of such declarations differ among those implementations. [...]
As ES5 does not define the behavior for function declarations inside blocks while allowing proprietary extensions, there are technically no "rights" or "wrongs". Consider them "unspecified behavior" which is not portable across different ES5-compliant environments.
Those are easy to rewrite into portable code anyway:
Should the function declaration be hoisted to the top of the current function/global scope? Make sure the function declaration is not directly inside of a block.
Should the function be declared only when the block executes? Assign a function expression to a variable (var f = function() {};). Note that there is no hoisting and the variable is still accessible outside of the block (var declarations are function-level scoped).
As per ECMAScript 6, function declarations are block-scoped, so Firefox implements the correct behavior ES6-wise.
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.