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