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

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

В этом фрагменте кода есть синтаксическая ошибка, говорящая о том, что Identifier 'x' has already been declared. Очевидно, что мы не можем повторно объявить переменную с помощью let в одной области видимости. Но я не знаю, почему в этом фрагменте кода мы все равно получим эту ошибку, поскольку в ES6 параметр по умолчанию фактически создает другую область, называемую параметром environment.

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

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

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


person Chor    schedule 25.05.2019    source источник
comment
Область обоих параметров не является отдельной областью, но создает переменную в области вашей функции через механизм параметров.   -  person slebetman    schedule 25.05.2019
comment
@slebetman Что вы подразумеваете под механизмом параметров?   -  person melpomene    schedule 25.05.2019
comment
Но переменная параметра находится в области параметров, а не в области функции. Есть 2 разные переменные, хотя они имеют одно и то же имя.   -  person Chor    schedule 25.05.2019
comment
```вар х = 1; function foo(x, y = function() { x = 2; }) { var x = 3; у(); консоль.лог(х); } Фу(); ``` @slebetman Вывод предполагает, что существует 2 разных x в 2 разных областях.   -  person Chor    schedule 25.05.2019
comment
@slebetman нет, просто нет.   -  person Jonas Wilms    schedule 25.05.2019


Ответы (3)


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

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

person Jonas Wilms    schedule 25.05.2019
comment
Я не вижу 9.2.15, только 9.3. - person melpomene; 25.05.2019
comment
9.2.15 FunctionDeclarationInstantiation ( func, argumentsList) спецификации ES-262. (не могу предоставить ссылку, я работаю с локально скопированным PDF-файлом, так как он загружается намного быстрее) - person Jonas Wilms; 25.05.2019
comment
Это 9.2.12 в ES6 (2015). Это точная часть OP, на которую ссылаются и цитируют. - person melpomene; 25.05.2019
comment
Ну тогда... раздел довольно большой и сложный. - person Jonas Wilms; 25.05.2019
comment
Согласовано. Лично я нахожу спецификацию JS нечитаемой/непонятной, поэтому я предполагаю, что цитируемый фрагмент не означает то, что кажется, но я думаю, что он заслуживает более подробного объяснения. - person melpomene; 25.05.2019
comment
Я думаю, что включил столько, сколько нужно... На ваш взгляд, чего-то не хватает? - person Jonas Wilms; 25.05.2019
comment
Лично я хотел бы увидеть аннотированную версию этого раздела спецификации, объясняющую, что означает каждый шаг алгоритма. (Кроме того, я не отрицал этот ответ, если вас это беспокоит.) - person melpomene; 25.05.2019
comment
Я добавил прохождение. - person Jonas Wilms; 25.05.2019
comment
@Jonas Wilms Спасибо, Джонас. Но я все еще чувствую, что это настолько сложно, что вообще не могу понять. Посмотрите на эту картинку myblog-1258623898.cos.ap-chengdu.myqcloud.com/code.png. Я вполне понимаю, как работает контекст выполнения в ES6, когда во время него не существует параметра по умолчанию. это сложно сделать, когда существует параметр по умолчанию. Не могли бы вы объяснить, как контексты выполнения работают с кодом фрагмента из вопроса? Я думаю, это будет легко понять, используя псевдокод для объяснения этого вопроса. - person Chor; 26.05.2019

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

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

foo(5);

Как будто код на самом деле написан так:

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

foo(5);

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

введите здесь описание изображения

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

person Sagiv b.g    schedule 25.05.2019
comment
Насколько я могу судить, это на самом деле не отвечает на вопрос. Связанный ответ совершенно не имеет значения. - person melpomene; 25.05.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();

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

person RiverOceanSea    schedule 25.05.2019
comment
Параметр находится в той же области, что и тело функции. Необходимо объяснить, почему. Тем более, что в части спецификации, цитируемой OP, похоже, говорится, что она работает по-разному для параметров со значениями по умолчанию. Ваш ответ не касается этого. - person melpomene; 25.05.2019
comment
@melpomene Что? Как так? В самом предложении я сказал под капотом, что код объявляет var x = 2, когда функция выполняется... Разве это не объяснение, почему параметр находится в той же области, что и тело функции? Как бы вы еще это объяснили? – - person RiverOceanSea; 27.05.2019
comment
Вам необходимо сослаться на спецификацию языка. Или, в терминах Википедии, [необходима цитата]. - person melpomene; 27.05.2019
comment
@melpomene 1/2 Я не знаю, кто такой ОП, но я не вижу противоречия в другом ответе, который цитирует документацию. Он сказал... Затем все переменные и параметры функции объявляются и инициализируются в этой области, что означает, что все параметры копируются. Если это противоречие, я был бы открыт для изучения, почему? Я также отредактировал свой ответ, чтобы подчеркнуть, что он дает информацию, которая поможет спрашивающему понять, что происходит/как он может получить доступ к переменным. - person RiverOceanSea; 27.05.2019
comment
... 2/2 Так как это довольно корректирующий вопрос, можно предположить, что такие фразы, как If IsConstantDeclaration of d истинны, то 1. Выполнить ! lexEnvRec.CreateImmutableBinding(dn, true). может звучать как тарабарщина для многих пользователей и не увеличивать фактическое понимание того, что происходит. Кажется, что также важно говорить о текущем уровне понимания, которым обладает спрашивающий. - person RiverOceanSea; 27.05.2019
comment
Для меня они звучат как тарабарщина, поэтому мне нравится видеть объяснение этих слов в ответах. - person melpomene; 27.05.2019
comment
Да, но вы гораздо более продвинуты, чем вопрошающий. Спрашивающий не спрашивал о спецификациях под капотом. Он спрашивал, почему он получил справочную ошибку. #коммуникация - person RiverOceanSea; 28.05.2019