Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 731
Royi Namir Avatar asked Oct 04 '17 07:10

Royi Namir


People also ask

What is closure scope in JavaScript?

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.

What is the scope of a function parameter JavaScript?

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.

Does JavaScript provide block scope?

With ES2015, in addition to function-level scoping, JavaScript also supports block-level scoping with the help of the let keyword & const keyword.

Are all functions in JavaScript closures?

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.


2 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.

like image 184
Emil S. Jørgensen Avatar answered Oct 11 '22 03:10

Emil S. Jørgensen


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.

like image 4
Redu Avatar answered Oct 11 '22 03:10

Redu