;(function(a){
if(true){
function a(){}
}
console.log(a) // 1
})(1)
;(function(){
var a = 0
if(true){
function a(){}
}
console.log(a) // function a(){}
})()
Why can't a function in a block-level scope change a formal parameter?
I don't understand why you want to do this, but let's explore this for the sake of better understanding a corner case of JavaScript. Sometimes that helps us understand the fundamentals of the language better.
For sake of discussion, let's consider your two examples, as Case A and Case B, respectively:
// Case A - argument a is not overwritten
;(function(a){
if(true){
function a(){}
}
console.log(a) // 1
})(1)
// Case B - var a is overwritten
;(function(){
var a = 0
if(true){
function a(){}
}
console.log(a) // function a(){}
})()
Why a function declaration in block-level scope cannot change a formal parameter
In JavaScript var
does not have block scope, it has functional scope, so you cannot assume that every time you see { }
that it creates a scope for the vars declared within it. Basically, function blocks work differently than other blocks as used by conditions and iterations.
Recently, with ES2015, block-scoped variables were introduced with the keywords let
and const
. Nevertheless, scoping is not straightforward in JS, so you must understand how different keywords create variables and how they are scoped within different block structures, and also how strict-mode affects that behavior.
As it turns out, Case B is a fluke and an accident of the way that var
and function () {}
declarations work in non-strict mode.
First, in all JavaScript (strict mode included), functions defined with function declarations, e.g. function foo() {...}
are hoisted to the top of the current block-level scope! This means that, within scope, you can never overwrite a var
by a function declaration.
// Case B modified
;(function(){
console.log(a) // function a(){}
var a = 0; // overwrites value of 'a'
function a(){}; // will be hoisted to top of block-level scope
console.log(a) // 0
})()
Secondly, within conditional if
blocks, function declarations are hoisted to the top of any block they are defined within, not the surrounding function block.
Third, in sloppy mode (non-strict), JavaScript, for function declarations defined with an if
block will allow that value to overwrite the values of variables declared with var
prior to that block.
// showing behavior of points #2 and #3:
;(function(){
console.log(a); // undefined
var a = 0;
console.log(a); // 0
if(true) {
console.log(a); // function a(){...} - a() was hoisted to top of if block
function a() {};
})();
console.log(a); // function a(){} - function declaration allowed to overwrite var declared above in surrounding function scope
})();
So, you've discovered a strange corner case where function declaration hoisting and scoping behave badly in non-strict mode. It won't do this in strict mode, see next section below on that.
Function arguments behave more like variables defined with let
than var
s, so that's why Case A doesn't behave like Case B. It's not so much that block-level scope function declarations cannot change a formal parameter as it is that it just shouldn't do that anyway, even for vars. Case A is how it ought to behave.
Note, that if you use let
instead of var
things behave more consistently, even in sloppy mode:
// Case B using 'let' instead
;(function(){
let a = 0;
console.log(a); // 0
if(true) {
console.log(a); // function a(){}
function a() {};
}
console.log(a); 0
})();
Also, let
just behaves better in general, for instance, even in sloppy mode, attempting to redefine a variable already declared with let
is not allowed:
// just try this!
let a = 0;
function a() {} // this will throw a syntax error
Difference between Node and Browser? No. It's about strict mode.
Some commenters noted a difference between Node.js and JavaScript in a browser on this issue. The claim was that:
// in a browser
console.log(a) // Case A: 1
console.log(a) // Case B: function a(){}
// in node
console.log(a) // Case A: 1
console.log(a) // Case B: 0
But in fact, I just tested with both Codepen in a browser, and with Node (8.11.3 and 10.5.0) on my local, and both returned these results:
// in Node and browser
console.log(a) // Case A: 1
console.log(a) // Case B: function a(){}
However, when you set the use strict
directive, then you get the following results, but the same in both Node and browser:
// with 'use strict, in Node and browser
console.log(a) // Case A: 1
console.log(a) // Case B: 0
Recommendations on Conditional Function Declarations
Basically, I would not do this, unless my function always returned a function. In other words, I wouldn't write a function or method, in most cases, to at times return a primitive value and other times return a function.
But let's assume you wanted to do that. Then as a matter of course, I would always:
let
and const
instead of var
See section 'Variables' here
And I would, in this case, not use a function declaration, but assign a function expression to my variable instead:
;(function(){
let a = 0;
console.log(a); // 0
if(true) {
console.log(a); // 0
a = function () {}; // assign 'a' the value of the function
}
console.log(a); // function () { ... }
})();
For my own sake, I created a Codepen to help with all of this: https://codepen.io/mrchadmoore/pen/jpEKaR?editors=0012
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With