1

function f1(x = 2, f = function() {x = 3;}) {
  let x = 5;
  f();
  console.log(x);
}
f1();

In this snippet code there is a syntax error saying that Identifier 'x' has already been declared. It is obvious that we can't redeclare a variable using let in one scope. But I don't know why in this snippet code we will still get this error since in ES6 default parameter will actually create another scope called parameter environment.

http://www.ecma-international.org/ecma-262/6.0/#sec-functiondeclarationinstantiation

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.

So here we have a global scope, a parameter scope and a function scope. In the parameter scope we declare a parameter named x while we also declare another variable named x in the function scope. Although these 2 have the same name, they exist in a different scope. Why in this condition will we still get a syntax error suggesting that duplication isn't allowed?

melpomene
  • 84,125
  • 8
  • 85
  • 148
Chor
  • 833
  • 1
  • 5
  • 14
  • Both parameter scope is not a separate scope but creates a variable in your function scope via the parameter mechanism – slebetman May 25 '19 at 06:12
  • @slebetman What do you mean by "the parameter mechanism"? – melpomene May 25 '19 at 06:17
  • But the parameter variable is in the parameter scope instaed of function scope。There are 2 different variables although they have the same name. – Chor May 25 '19 at 06:17
  • ``` var x = 1; function foo(x, y = function() { x = 2; }) { var x = 3; y(); console.log(x); } foo(); ``` @slebetman The output suggests that there are 2 different x existing in 2 different scopes. – Chor May 25 '19 at 06:21
  • @slebetman no, just no. – Jonas Wilms May 25 '19 at 13:08

3 Answers3

1

Yes, you are right. There are three different scopes involved here (one for the first parameter, one for the second, and one for the body).

However, after the parameters were initialized (in their own scope) they get copied to a new lexical environment (in which the body will be executed then) (can be found in 9.2.15 of the spec).

That means that the parameter names not only exist in the scope of the parameter, but also in the scope the body is evaluated in, thus using the same name inside of the body is a redeclaration of the variable, resulting in an error (with let / const).


Here is a walkthrough of the spec:

When a function gets parsed, it will create a function object, which contains some internal properties:

[[Environment]]: Thats a reference to the outer scope, so that you can access variables of the outer scope inside a function (this also causes the closuring behaviour, [[Environment]] might reference an environment that is not active anymore).

[[FormalParameters]]: The parsed code of the parameters.

[[ECMAScriptCode]]: The code of the functions body.

Now when you call a function (9.2.1 [[Call]]), it'll allocate an environment record on the callstack, and will then

Let result be OrdinaryCallEvaluateBody(F, argumentsList).

call the function. Thats where 9.2.15 comes in. First of all, it will declare all parameters in the functions body environment:

[Initialize local helper variables]

21. For each String paramName in parameterNames, do

    i. Perform ! envRec.CreateMutableBinding(paramName, false).

 [Rules for initializing the special "arguments" variable]

Then it will initialize all the parameters. Parameters are really complicated, cause there are also rest parameters. Therefore arguments have to be iterated to possibly turn them into arrays:

24. Let iteratorRecord be CreateListIteratorRecord(argumentsList)

25. If hasDuplicates is true, then

   a. Perform ? IteratorBindingInitialization for formals with iteratorRecord and undefined as arguments.

26. Else,

   a. Perform ? IteratorBindingInitialization for formals with iteratorRecord and env as arguments.

Now IteratorBindingInitialization is defined in 13.3.3.8:

It'll basically evaluate the default value, and will initialize a binding in the current environment.

When all parameters are initialized, the functions bodys environment can be prepared. As mentioned in the comment you quoted, if there are no parameter initializers no new environment is required. If there is however a default value somewhere, a new environment will be created.

27. If hasParameterExpressions is false, then

 [...]

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

Then a "new scope" gets created, in which the body gets evaluated:

    b. Let varEnv be NewDeclarativeEnvironment(env).

    c. Let varEnvRec be varEnv's EnvironmentRecord.

    d. Set the VariableEnvironment of calleeContext to varEnv.

Then all the variables and parameters of the function get declared and initialized in that scope, meaning that all parameters get copied:

   f. For each n in varNames, do

     2. Perform ! varEnvRec.CreateMutableBinding(n, false).

     3. If n is [a regular variable declared with "var"], let 
        initialValue be undefined.

     4. Else, [if it is a parameter]

         a. Let initialValue be ! envRec.GetBindingValue(n, false)

     5. Call varEnvRec.InitializeBinding(n, initialValue).

 [Other rules for functions in strict mode]

 31. [varEnv gets renamed to lexEnv for some reason]

After that, all inner variables with let / const get declared (but not initialized, they will be initialized when the let gets reached).

34. Let lexDeclarations be the LexicallyScopedDeclarations of code.

35. For each element d in lexDeclarations, do

     a. NOTE: A lexically declared name cannot be the 
                  same as a function/generator declaration, formal
                   parameter, or a var name. Lexically declared 
                   names are only instantiated here but not initialized.

     b. For each element dn of the BoundNames of d, do

        i. If IsConstantDeclaration of d is true, then

          1. Perform ! lexEnvRec.CreateImmutableBinding(dn, true).

       ii. Else,

         1. Perform ! lexEnvRec.CreateMutableBinding(dn, false).

Now as you can see, CreateMutableBinding gets called here, and as specified in 8.1.1.1.2 ...

The concrete Environment Record method CreateMutableBinding for declarative Environment Records creates a new mutable binding for the name N that is uninitialized. A binding must not already exist in this Environment Record.

So as the parameter was already copied into the current environment, calling CreateMutableBinding with the same name will fail.

melpomene
  • 84,125
  • 8
  • 85
  • 148
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • I don't see a 9.2.15, only 9.3. – melpomene May 25 '19 at 14:12
  • `9.2.15 FunctionDeclarationInstantiation ( func, argumentsList)` of the ES-262 spec. (can't provide a link, I'm working with a locally copied PDF as that loads way faster) – Jonas Wilms May 25 '19 at 14:16
  • That's 9.2.12 in ES6 (2015). It's the exact part OP linked to and quoted from. – melpomene May 25 '19 at 14:35
  • Well then, ... the section is quite large and complicated. – Jonas Wilms May 25 '19 at 14:40
  • Agreed. Personally I find the JS spec to be unreadable / incomprehensible, so I assume the quoted bit does not mean what it seems to mean, but I think it deserves a more detailed explanation. – melpomene May 25 '19 at 14:48
  • 1
    I think I included as much as necessary ... Is there anything missing in your eyes? – Jonas Wilms May 25 '19 at 15:14
  • Personally I'd like to see an annotated version of that section of the spec, explaining what each step of the algorithm means. (Also, I didn't downvote this answer if that's what you're concerned about.) – melpomene May 25 '19 at 15:16
  • 1
    I added a walkthrough. – Jonas Wilms May 25 '19 at 18:09
  • @Jonas Wilms Thanks Jonas.But I still feel it is so complicated that can‘t understand at all.Check this picture https://myblog-1258623898.cos.ap-chengdu.myqcloud.com/code.png .I can quite understand how execution context in ES6 works when there is not default parameter existing while it is hard to do that when the default parameter exists.Could you please explain how the execution contexts works with the snippet code from the question?I think it will be easy to understand when using pseudocode to explain this question. – Chor May 26 '19 at 15:27
0

Regarding the syntax error: A parameter is in the same scope as the body of a function. So, if you have a parameter named x, under the hood the code is essentially declaring var x when the function is executed. Or var x = 2 for your default param. (This might not be the exact mechanism that computer engineers would describe, but for working with the code, that is essentially what is going on).

If you change the argument "x" to "z" instead (see below) the syntax error will go away.

function f1(z = 2, f = function() {x = 3;}) {
   let x = 5;
   f();
   console.log(x);
 }
 f1();

Regarding the f() function not affecting the value of x that is console.log()'d .. I think that it has something to do with where the function is declared, and the order in which the JavaScript is compiled having an effect on the scope of the function when it is called.

For example, this code does not change the value of x:

 function f() {
     x = 3;  
 }

 function f1(z = 2) {
    var x = 5;
    f();
    console.log(x);
  }
  f1();

But this code does:

 function f1(z = 2) {
    function f() {
       x = 3;  
    }
    var x = 5;
    f();
    console.log(x);
  }
  f1();
miken32
  • 42,008
  • 16
  • 111
  • 154
Maiya
  • 932
  • 2
  • 10
  • 26
  • 1
    "*A parameter is in the same scope as the body of a function.*" You need to explain why. Especially since the part of the spec quoted by OP seems to say it works differently for parameters with default values. Your answer doesn't address this. – melpomene May 25 '19 at 14:15
  • @melpomene What? How so? In the *very* next sentence, I said "under the hood, the code is declaring "var x = 2" when the function is executed ... Isn't this the explanation *why* a parameter is in the same scope as the body of the function? How else would you explain this? – – Maiya May 27 '19 at 00:19
  • You need to reference the language specification. Or in Wikipedia terms, [citation needed]. – melpomene May 27 '19 at 06:14
  • @melpomene 1/2 I don't know who OP is, but I don't see the contradiction in the other answer that cites documentation. He said "... Then all the variables and parameters of the function get declared and initialized in that scope, meaning that all parameters get copied." If this is a contradiction, I'd be open to learning why? I also edited my answer to emphasize that it is giving information that is practical for the questioner to understand what is going on/ how they can access the variables. – Maiya May 27 '19 at 17:34
  • ... 2/2 Since it is a fairly remedial question, one might assume that phrases like "If IsConstantDeclaration of d is true, then 1. Perform ! lexEnvRec.CreateImmutableBinding(dn, true)." might sound like Gibberish to many users, and not increase actual understanding of what is going on. It seems like it is also important to speak to the current level of understanding that the questioner has. – Maiya May 27 '19 at 17:35
  • They sound like gibberish to me, which is why I like to see an explanation of those words in answers. – melpomene May 27 '19 at 18:27
  • Yes, but you are much more advanced than the questioner. The questioner was not asking about under the hood spec. He was asking why he got a reference error. #communication – Maiya May 28 '19 at 03:08
0

When a function is invoked, a new execution context is created. Each execution context has its own local memory. Arguments passed to a function, are "saved" in the function's local memory.
Given this snippet:

function foo(x) {
    console.log(x);
}

foo(5);

Its as if the code is actually written like this:

function foo(x) {
    // var x = 5;
    console.log(x);
}

foo(5);

Default parameters may act a bit differently behind the scenes, the labels are still locally scoped though.
As far as i know, they may have a different environment record but not an actually a different scope block for example. hence the error, a let variable can not be re-declare in the same block scope.

enter image description here

I recommend reading this SO answer about scopes

Sagiv b.g
  • 30,379
  • 9
  • 68
  • 99
  • 1
    As far as I can tell, this doesn't really answer the question. The linked answer is entirely irrelevant. – melpomene May 25 '19 at 14:13