So... ES6¹ (which happens to be standardized a few hours ago) brings default parameters for functions similar to those in PHP, Python etc. I can do stuff like:
function foo (bar = 'dum') {
return bar;
}
foo(1); // 1
foo(); // 'dum'
foo(undefined); // 'dum'
MDN says that the default value for the parameter is evaluated at call time. Which means each time I call the function, the expression 'dum'
is evaluated again (unless the implementation does some weird optimizations which we don't care about).
My question is, how does this
play into this?
let x = {
foo (bar = this.foo) {
return bar;
}
}
let y = {
z: x.foo
}
x.foo() === y.z(); // what?
The babel transpiler currently evaluates² it as false
, but I don't get it. If they are really evaluated at call time, what about this:
let x = 'x from global';
function bar (thing = x) {
return thing;
}
function foo () {
let x = 'x from foo';
return bar();
}
bar() === foo(); // what?
The babel transpiler currently evaluates³ it as true
, but I don't get it. Why does bar
not take the x
from foo
when called inside foo
?
Yes, the parameter initializers are evaluated at call time. It's complicated, but the steps are basically as follows:
with a new environment in the "closure scope" of the called function
thisBinding
is initialisedarguments
object is created an boundIn the course of this, initialisers are evaluated
undefined
let
andconst
variables in the function body are createdSo parameter initialisers do have access to the
this
and thearguments
of the call, to previously initialised other parameters, and everything that is in their "upper" lexical scope. They are not affected by the variables declared in the function body (though they are affected by all the other parameters, even if in their temporal dead zone).Because
x
is a local variable thatbar
does not have access to. We're so lucky that they are not dynamically scoped! The parameter initialisers are not evaluated at the call site, but inside the called function's scope. In this case, thex
identifier is resolved to the globalx
variable.When they say "evaluated at call time", I think they're referring to a call-by-name expression. Here's how babel outputs your third example:
Since
var x
is inherited within the lexical scope ofbar
from the global scope, that is the scope in which it is used.Now, consider the following:
Which transpiles to
Notice how here,
id
is called inside the function, rather than being cached and memoized at the time the function is defined, hence the call-by-name rather than call-by-value.So the behavior is actually correct in your second example. There is no
y.foo
and sincethis
is dynamically scoped in Javascript (i.e. it varies based on the receiver of a given function invocation), wheny.z()
looks forthis.foo
, it will look for it iny
, soy.z()
will returnundefined
, whilex.foo()
will just return thefoo
function itself.If you do want to bind to the receiver you can bind
foo
tox
when you assign it. Then it should work as expected.Sorry if any of this is unclear; let me know in the comments and I'd be happy to clarify! :)