Почему по-прежнему повторяется ошибка, даже если я объявляю переменную в 2 разных областях? - PullRequest
1 голос
/ 25 мая 2019

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

Если формальные параметры функции не содержат инициализаторов значений по умолчанию, то объявления тела создаются в той же записи среды, что и параметры.Если существуют инициализаторы параметров по умолчанию, то для объявлений тела создается вторая запись среды.Формальные параметры и функции инициализируются как часть FunctionDeclarationInstantiation.Все остальные привязки инициализируются во время вычисления тела функции.

Итак, здесь у нас есть глобальная область действия, область действия параметра и область действия функции.В области действия параметра мы объявляем параметр с именем x, в то время как мы также объявляем другую переменную с именем x в области действия функции.Хотя эти 2 имеют одно и то же имя, они существуют в другом объеме.Почему в этом состоянии мы все еще получим синтаксическую ошибку, предполагающую, что дублирование не разрешено?

Ответы [ 3 ]

1 голос
/ 25 мая 2019

Да, вы правы. Здесь задействованы три различных области (одна для первого параметра, одна для второго и одна для тела).

Однако после инициализации параметров (в своей области видимости) они копируются в новую лексическую среду (в которой будет выполняться тело) (можно найти в 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]

Затем будут инициализированы все параметры. Параметры действительно сложны, потому что есть и остальные параметры. Поэтому аргументы должны быть повторены, чтобы возможно превратить их в массивы:

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:

.

Он в основном оценивает значение по умолчанию и инициализирует привязку в текущей среде.

Когда все параметры инициализированы, можно подготовить функции тела. Как упоминалось в цитируемом вами комментарии, если нет инициализаторов параметров, новая среда не требуется. Однако если где-то есть значение по умолчанию, будет создана новая среда.

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

Конкретный метод записи среды CreateMutableBinding для декларативных записей среды создает новый изменяемая привязка для имени N, которое не инициализировано. В этой записи среды еще не должно быть привязки.

Так как параметр уже был скопирован в текущую среду, вызов CreateMutableBinding с тем же именем завершится неудачей.

0 голосов
/ 25 мая 2019

Когда вызывается функция, создается новый контекст выполнения.Каждый контекст выполнения имеет свою собственную локальную память.Аргументы, передаваемые в функцию, «сохраняются» в локальной памяти функции.
Учитывая этот фрагмент:

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

foo(5);

Это выглядит так, как будто код на самом деле написан так:

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

foo(5);

Параметры по умолчанию могут действовать немного закулисно , хотя метки все еще имеют локальную область действия.
Насколько я знаю, они могут иметь другую запись среды, но на самом деле не другую область видимости.блок например.следовательно, ошибка, переменная let не может быть повторно объявлена ​​в той же области видимости блока.

enter image description here

Я рекомендую прочитать это SOответ о примерах

0 голосов
/ 25 мая 2019

Относительно синтаксической ошибки: параметр находится в той же области, что и тело функции. Итак, если у вас есть параметр с именем x, под капотом код будет по существу , объявляя var x при выполнении функции. Или var x = 2 для вашего параметра по умолчанию. (Это может быть не точный механизм, который описывают компьютерные инженеры, но для работы с кодом, по сути, это происходит).

Если вместо этого вы измените аргумент "x" на "z" (см. Ниже), синтаксическая ошибка исчезнет.

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

Относительно функции f (), не влияющей на значение x, которое является console.log () 'd .. Я думаю, что это как-то связано с тем, где объявлена ​​функция, и с порядком, в котором компилируется JavaScript оказывает влияние на область действия функции при ее вызове.

Например, этот код не меняет значение x:

 function f() {
     x = 3;  
 }

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

Но этот код делает:

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

** (Если вы проголосуете отрицательно, не могли бы вы объяснить, почему?)

...