Another thing that knocked me out of the chair when reading Nicholas' Understanding ES6 was this little excerpt on default parameters:
Perhaps the most interesting feature of default parameter arguments is that the default value need not be a primitive value. You can, for example, execute a function to retrieve the default parameter:
function getCallback() {
return function() {
// some code
};
}
function makeRequest(url, timeout = 2000, callback = getCallback()) {
// the rest of the function
}
Seems pretty clear at first. But what about that callback = getCallback()
? Surely, getCallback()
looks like a function call, and since it's not part of a string (eval, Function, etc.) it must be executed right there and then, right?
Quick check in FF proves me wrong:
function callback() { console.log('callback executed'); return function(){} }
function f1(x = callback) {
console.log('f1 executed');
console.log(x())
}
function f2(x = callback()){
console.log('f2 executed');
console.log(x());
}
// nothing is executed here
f1();
"f1 executed"
"callback executed"
f2();
"callback executed"
"f2 executed"
Wow, ok. So a presence of callback()
in code no longer means that it's a function call. Depending on where it is — such as in formal parameters — it could actually be a... sort of a deferred function call.
Notice how "callback executed" is not called all the way until function is actually invoked.
So both x = callback
and x = callback()
will execute at the time when function is invoked and before function code is executed.
Another interesting implication is IIFE:
function f3(x = (function(){ console.log('callback executed'); return function(){} })()) {
console.log('f3 executed');
console.log(x())
}
f3();
"callback executed"
"f3 executed"
Yes, that IIFE — Immediately Invoked Function Expression — is, in fact, no longer immediate 
I started looking in spec to find how exactly something like this:
function f(x = g()) { }
..is possible (grammar-wise).
After some digging, and starting from FormalParameters
production, I eventually made my way all the way to... CallExpression
, which proves that x=g()
is indeed possible as part of formal parameters:
FormalParameters ->
FormalParameterList ->
FormalsList ->
FormalParameter ->
BindingElement ->
SingleNameBinding ->
BindingIdentifier Initializer
BindingPattern Initializer (opt)
ObjectBindingPattern Initializer (opt)
{ ... } = AssignmentExpression
ArrayBindingPattern Initializer (opt)
[ ... ] = AssignmentExpression
BindingIdentifier = AssignmentExpression
...
ConditionalExpression
[... tons of operators ...]
CallExpression
But what about actual runtime semantics? Where in spec does it say when and how those f = callback
and f = callback()
are executed?
After more searching, I got to FunctionDeclarationInstantiation, which seemed relevant:
9.2.13 FunctionDeclarationInstantiation(func, argumentsList, env )
It even had this nice descriptive note on top:
"When an execution context is established for evaluating an ECMAScript function a new Function Environment Record is created and bindings for each formal parameter are instantiated in that environment record. Each declaration in the function body is also instantiated. If the function’s formal parameters do not include any default value initializers then the body declarations are instantiated in the same environment record as the parameters. If default value parameter initializers exist, a second environment record is created for the body declarations. Formal parameters and functions are initialized as part of FunctionDeclarationInstantiation. All other bindings are initialized during evaluation of the function body."
Ahha, nice and clear. This reminded me of NFE and how its identifier is injected in an intermediate scope (to make it visible to the inner scope but not the outer one).
However, reading FunctionDeclarationInstantiation proved challenging. The relevant bits:
...
5. Let formals be the value of the [[FormalParameters]] internal slot of func.
([[FormalParameters]] was set earlier via FunctionInitialize)
- Let parameterNames be the BoundNames of formals.
...
- For each String paramName in parameterNames, do
c. If alreadyDeclared is false, then
i. Let status be the result of calling envRec’s CreateMutableBinding concrete method passing paramName as the argument.
...
- If hasParameterExpressions is false, then
...
- Else
"NOTE A separate environment record is needed to ensure that closures created by expressions in the formal parameter list do not have visibility of declarations in the function body."
Ok, so far so good — essentially, for each formal parameter a binding is created. But then:
...
f. For each n in varNames, do
i. ...
2. Let status be the result of calling varEnvRec’s CreateMutableBinding
concrete method passing n as the argument.
Ok, so for each varNames of code we create mutable binding. But wait. Why are we iterating over varNames of function code and not over formal parameters?! And where are those initializer functions actually executed?
The 1st one took me a while to figure out... And then it hit me — why am I thinking that "code" refers to function body only? Could it be that it somehow includes arguments as well?
Lo and behold — 10.2 Types of Source code
Function code is source text that is parsed to supply the value of the [[ECMAScriptCode]] internal slot (see 9.1.14) of function and generator objects. It also includes the code that defines and initializes the formal parameters of the function. The function code of a particular function or generator does not include any source text that is parsed as the function code of a nested FunctionBody, GeneratorBody, ConciseBody, or ClassBody.
Dear god... well, that explains it.
So varNames (=VarDeclaredNames of Code) apparently include formal parameters. But searching for VarDeclaredNames as they relate to formal parameters leaves me in the dark. I don't see where spec explains what VarDeclaredNames of Code are; especially in context of formal parameters.
As for 2nd question — function initialization — step 34 seems to have the answers:
- For each production f in functionsToInitialize, do
a. Let fn be the sole element of the BoundNames of f.
b. Let fo be the result of performing InstantiateFunctionObject for f with argument lexEnv.
c. Let status be the result of calling varEnvRec’s SetMutableBinding concrete method passing fn, fo and false as the arguments.
...
How did we get functionsToIntialize? In step 15:
- For each d in varDeclarations, in reverse list order do
...- Insert d as the first element of functionsToInitialize.
(varDelcarations = VarScopedDeclarations of code)
What I still don't understand is how exactly something like f(x = callback())
is recognized as function to initialize during function instantiation. According to step 15, functionsToInitialize should only have function declarations. And callback()
certainly shouldn't be parsed as one.
Does anyone know or can figure it out? Am I missing something obvious?