This was presented yesterday at TC39. You can find the gist here:
var p = () => console.log(f);
{
p(); // undefined
console.log(f); // function f(){}
f = 1;
p(); // undefined
console.log(f); // 1
function f(){}
p(); // 1
console.log(f); // 1
f = 2;
p(); // 1
console.log(f); // 2
}
Could someone please explain to me how this thing works? For the record it's only working in non-strict mode.
Thank you.
I will not claim to understand all the subtleties, but the key thing about this the almost bizarre contortions of Annex B's §B.3.3.1.
That code is effectively this, where f1
is a second copy of f
specific to the lexical environment of the block (hence let
below):
var p = () => console.log(f);
{
let f1 = function f(){};; // Note hoisting
p(); // undefined
console.log(f1); // function f(){}
f1 = 1;
p(); // undefined
console.log(f1); // 1
var f = f1; // !!!
p(); // 1
console.log(f1); // 1
f1 = 2;
p(); // 1
console.log(f1); // 2
}
And of course, thanks to var
hoisting, both p
and f
are effectively declared at the top of the code snippet with the initial value undefined
:
var f = undefined;
var p = undefined;
p = () => console.log(f);
{
let f1 = function f(){};; // Note hoisting
p(); // undefined
console.log(f1); // function f(){}
f1 = 1;
p(); // undefined
console.log(f1); // 1
f = f1; // !!!
p(); // 1
console.log(f1); // 1
f1 = 2;
p(); // 1
console.log(f1); // 2
}
The key bit from B.3.3.1 is that it transfers the value of the inner f
(which I've called f1
above) to the outer one (in the below, F is the string "f"
, the name of the function being declared):
3. When the FunctionDeclaration f is evaluated, perform the following steps in place of the FunctionDeclaration Evaluation algorithm provided in 14.1.21:
a. Let fenv be the running execution context's VariableEnvironment.
b. Let fenvRec be fenv's EnvironmentRecord.
c. Let benv be the running execution context's LexicalEnvironment.
d. Let benvRec be benv's EnvironmentRecord.
e. Let fobj be ! benvRec.GetBindingValue(F, false).
f. Perform ! fenvRec.SetMutableBinding(F, fobj, false).
g. Return NormalCompletion(empty).
Recall that the variable environment is function-wide, but the lexical environment is more constrained (to the block).
When it comes to trying to normalize function declarations in places where they were {invalid | unspecified} (choose your term), TC39 have a very treacherous path to navigate, trying to standardize behavior while not breaking existing code that may have relied on implemenation-specific behaviors from the past (which were mutually-exclusive, but TC39 is trying to strike a balance).
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