Does JS treat parameter list as a scope that can be closed upon?

Viewed 251

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.

3 Answers

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.

The function parameters have the same scope, and the latter parameters can be set using the previous adjacent parameter values.

Related