javascript - 为什么即使我在 2 个不同的范围内声明变量仍然存在重复错误?

标签 javascript ecmascript-6 scope language-lawyer default-parameters

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

在这段代码中有一个语法错误表明Identifier 'x' has already been declared。很明显,我们不能在一个范围内使用 let 重新声明一个变量。但我不知道为什么在这段代码中我们仍然会收到此错误,因为在 ES6 中默认参数实际上会创建另一个名为参数环境的范围。

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.

所以这里我们有一个全局作用域、一个参数作用域和一个函数作用域。在参数范围内,我们声明了一个名为 x 的参数,同时我们还在函数范围内声明了另一个名为 x 的变量。尽管这两个名称相同,但它们存在于不同的范围内。为什么在这种情况下我们仍然会收到提示不允许重复的语法错误?

最佳答案

是的,你是对的。这里涉及三个不同的范围(一个用于第一个参数,一个用于第二个,一个用于主体)。

然而,在参数被初始化(在它们自己的范围内)之后,它们被复制到一个新的词法环境(然后主体将在其中执行)(可以在规范的 9.2.15 中找到)。

这意味着参数名称不仅存在于参数的范围内,而且还存在于计算主体的范围内,因此在主体内使用相同的名称是 重新声明变量,导致错误(使用 let/const)。


这是规范的演练:

当一个函数被解析时,它会创建一个函数对象,其中包含一些内部属性:

[[Environment]]:这是对外部作用域的引用,因此您可以在函数内部访问外部作用域的变量(这也会导致关闭行为,[[Environment]] 可能会引用一个不是活跃了)。

[[FormalParameters]]:参数的解析代码。

[[ECMAScriptCode]]: 函数体代码。

现在当你调用一个函数时(9.2.1 [[Call]]),它会在调用栈上分配一个环境记录,然后将

Let result be OrdinaryCallEvaluateBody(F, argumentsList).

调用函数。这就是 9.2.15 的用武之地。首先,它将在函数体环境中声明所有参数:

[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]

然后它将初始化所有参数。参数真的很复杂,因为还有rest参数。因此必须迭代参数才能将它们变成数组:

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.

现在 IteratorBindingInitialization 在 13.3.3.8 中定义:

它基本上会评估默认值,并会在当前环境中初始化绑定(bind)。

当所有参数都初始化后,函数体的环境就可以准备好了。正如您引用的评论中提到的,如果没有参数初始值设定项,则不需要新的环境。但是,如果某处存在默认值,则会创建一个新环境。

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.

然后创建一个“新范围”,在其中对主体进行评估:

    b. Let varEnv be NewDeclarativeEnvironment(env).

    c. Let varEnvRec be varEnv's EnvironmentRecord.

    d. Set the VariableEnvironment of calleeContext to varEnv.

然后函数的所有变量和参数都在该范围内声明和初始化,这意味着所有参数都被复制:

   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]

之后,声明了所有带有 let/const 的内部变量(但未初始化,它们将在到达 let 时被初始化).

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

现在如您所见,CreateMutableBinding 在这里被调用,并且如 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.

因为参数已经被复制到当前环境中,调用同名的CreateMutableBinding将失败。

关于javascript - 为什么即使我在 2 个不同的范围内声明变量仍然存在重复错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56301703/

相关文章:

javascript - jQuery 在搜索中切换父级

javascript - 等待循环

javascript - 推送对象数组而不是其值

C++ - 将对象添加到 std::vector,在循环中实例化

css - 如何基于 CSS 选择器用另一个值覆盖 SASS 变量?

javascript - 在javascript中调用父属性和函数?

javascript - 如何访问 typescript 中的字段验证错误?

javascript - 谷歌地图中的 route 点

javascript - 需要bind或apply申请部分申请

javascript - 变量在 for 循环中没有改变 - JavaScript