I received a question about this snippet of code:
function def(first="oldValue" , second=function(){
return first;
}){
var first="updatedValue";
console.log('inside',first);
console.log('function',second());
}
When you run that function without any arguments (like def();
), you get this:
inside updatedValue
function oldValue
But if you change the code to this:
function def(first="oldValue" , second=function(){
return first;
}){
first="updatedValue"; // NOTE: removed the `var` here
console.log('inside',first);
console.log('function',second());
}
Now it prints:
inside updatedValue
function updatedValue
The question is, why is this happening?
NOTE: I strongly advise against venturing into these murky/confusing waters, where you create closures inside a parameter default value, and especially if you're going to then shadow those parameters as local function variables. This is way into the weeds and while it may be interesting for JS trivia, you should never rely on this kind of behavior in your programs. It's far too confusing to the vast majority of readers.
There's two layers of answer here. So, first:
We normally think of parameters and local variables as being the same -- coming from the same local function scope. But technically, they've always been in separate scopes. It's just that this is the first time (ES6+) we've been able to observe those different scopes.
When there's a var first = ..
in the function, it "shadows" the parameter variable (which is technically in its own scope), so that local variable is created separately from the parameter. That's why you get the two separate values. The default function value closes over the first
in the parameter scope, but the first
in the main function scope has a different value.
When you take the var
off, the first = ..
is just a plain assignment to a lexical variable, and it finds the one in the parameter scope to overwrite its value. That's why you get both printed values being the same in that case.
Wait. Re-read this (repeatedly if necessary) until you can convince yourself of this reasoning and be comfortable with it.
If that's good enough (or confusing enough!) for you, just take the blue pill and stop. Refer to my advice about "JS trivia" above and move on.
OK, so you want to take the red pill? Here we go.
Consider this function:
function def(first="oldValue" , second=function(){
return first;
}){
var first; // NOTE: no assignment, just declaration
console.log('inside',first);
console.log('function',second());
}
If you were using the logic we've just articulated, what would you expect that function to print out?
I think the most reasonable expectation would be:
inside undefined
function oldValue
Why? Because you've created the first
in the local scope, but it should just be in an undefined
state since it's not been assigned anything.
BUT! That's not what happens. It prints:
inside oldValue
function oldValue
Why?
Because of legacy expected behavior from before ES6, this special case means that the shadowed local variable declaration in the function is by default set to the value of the parameter, NOT overwritten by the implied = undefined
from var first;
as one would normally expect.
However, they are still two separate variables; they just happen to have the same values at the moment. As soon as you assign, you're changing only the local version of that variable:
function def(first="oldValue" , second=function(){
return first;
}){
var first;
// later
first = "updatedValue";
console.log('inside',first);
console.log('function',second());
}
It will now print:
inside updatedValue
function oldValue
Yay for confusing scope behaviors and legacy corner case exceptions!