Looking at this simple code which uses Lazy Expressions:
var x = 1;
function foo(x = 2, f = () => x) {
var x = 5;
console.log(f())
}
foo()
The output here is 2
.
I must say that I thought it should output 5.
However - this would've been logical if f
was closing over the parameter list scope - if it had a scope.
Because looking at this other example (which a bit related) :
var x = 5;
var f = function() {
return x;
}
x = 1
f();
console.log(x)
This will output 1. (Which is the expected result.).
Question
What's actually going here with the parameter list scope ? is there any scope here at all ?(at the parameter list)
I didn't find scope related info in the docs.
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function.
The parameter name is similar to declaring a variable name at the top of the function. So the scope of a parameter is the function it is a part of.
With ES2015, in addition to function-level scoping, JavaScript also supports block-level scoping with the help of the let keyword & const keyword.
But as explained above, in JavaScript, all functions are naturally closures (there is only one exception, to be covered in The "new Function" syntax). That is: they automatically remember where they were created using a hidden [[Environment]] property, and then their code can access outer variables.
Function parameters have scope.
In you first example you allocate a new x
variable, which is why it doesn't overwrite:
//Global x
var x = 1;
function foo(x = 2 /* Local scope x */ , f = () => x /* Local scope x bound to new function scope */ ) {
/* new local scope x. If you removed the "var", this would overwrite localscope x */
var x = 5;
/* All 3 x's accessed */
console.log(f(), x, window.x)
}
foo()
var x = 1;
function foo(x = 2, f = () => x) {
x = 5;
console.log(f(), x, window.x)
}
foo()
EDIT 1 - TypeScript
As answer to the comments. TypeScript compiles this ES6 version:
//Global x
var x = 1;
function foo(x = 2 /* Local scope x */ , f = () => x /* Local scope x bound to new function scope */ ) {
/* new local scope x. If you removed the "var", this would overwrite localscope x */
var x = 5;
/* All 3 x's accessed */
console.log(f(), x, window.x)
}
foo()
Into this:
//Global x
var x = 1;
function foo(x /* Local scope x */, f /* Local scope x bound to new function scope */) {
if (x === void 0) { x = 2; } /* Local scope x */
if (f === void 0) { f = function () { return x; }; } /* Local scope x bound to new function scope */
/* new local scope x. If you removed the "var", this would overwrite localscope x */
var x = 5;
/* All 3 x's accessed */
console.log(f(), x, window.x);
}
foo();
It does this because older browsers don't support parameter declaration, but it messes with the scope if compared to the straight ES6 version.
This is an interesting question.
It's best to refer Ecmascript 2017 specifications to understand how exactly the argument binding mechanism work.
When a function is being defined there are one or two Environment Records in action. The bindings set by the Environment Record(s) differ depending on whether the arguments have a default value or not. If the argument(s) have default values then, 2 Environment Records are in action. One for the parameter instantiations and one for the body declaration (such as variables, inner functions etc..).
Obviously when you do like;
function(x = 2, y = x){
...
}
there is a pre-assignment function at work and it has to have it's own context. So in case of;
function foo(x = 2, f = () => x) {
var x = 5;
console.log(f())
}
x
gets under closure at the time of the function parameters' definition.
So let's read the relevant part of the ECMA 2017 specs where it says;
9.2.12 FunctionDeclarationInstantiation(func, argumentsList)
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.
Also we are given an algorithm in detail how to implement this functionality if we ever need to sit and code our own JS engine. Step 27.a is interesting.
27 Else,
a. 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.
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