Как идентификатор может быть уже объявлен и не определен? - PullRequest
2 голосов
/ 16 января 2020

Я бездельничал в консоли JS и наткнулся на какое-то недоумение. Вторая ошибка (SyntaxError) имеет для меня смысл, я уже объявил (или попытался, по крайней мере) запрет, поэтому я не смогу объявить его снова. Тем не менее, я бы ожидал, что bar будет undefined в этом случае.

Как переменная может быть объявлена ​​и не определена? Кто-нибудь может объяснить, что происходит внутри?

let bar = fo.map(i => console.log(i)) //typo
VM2927:1 Uncaught ReferenceError: fo is not defined
    at <anonymous>:1:11
(anonymous) @ VM2927:1

let bar = foo.map(i => console.log(i)) //fix typo
VM2999:1 Uncaught SyntaxError: Identifier 'bar' has already been declared
    at <anonymous>:1:1
(anonymous) @ VM2999:1

bar
VM3019:1 Uncaught ReferenceError: bar is not defined
    at <anonymous>:1:1

.as-console-wrapper {max-height: 100% !important;}
<script>
  //typo:
  let bar = fo.map(i => console.log(i)) //Uncaught ReferenceError: fo is not defined
</script>
<script>
  //fix typo:
  let bar = foo.map(i => console.log(i)) //Uncaught SyntaxError: Identifier 'bar' has already been declared
</script>
<script>
  console.log(bar) //Uncaught ReferenceError: bar is not defined
</script>

Ответы [ 2 ]

2 голосов
/ 01 февраля 2020

На самом деле это вообще невозможно.

TL; DR: В вашем коде переменная bar равна объявлен и определен, но не инициализирован.


Первая ошибка верна: bar был объявлен дважды.

Также обратите внимание, что это время компиляции SyntaxError, так что это происходит до того, как код будет оценен, поэтому на него не влияет сгенерированное исключение внутри объявления переменной:

//SyntaxError

console.log('Evaluating code') //Never runs
let foo = 'bar'
let foo = 'baz'

Но вторая ошибка не столь очевидна: почему bar просто undefined?

После большого количества поиск в спецификации ECMAScript 6 , я нашел источник проблемы. Это «ошибка» (или, по крайней мере, ситуация, о которой не позаботились) в самом spe c, но, к счастью, это очень редко за пределами консоли JS.

Возможно, вы знаете что переменные let и const имеют так называемую временную мертвую зону , которая выбрасывает ReferenceError s при попытке поиска или назначения переменных до их объявления:

/* Just to make console fill the available space */
.as-console-wrapper{max-height:100% !important;}
<!-- Using separate scripts to show all errors -->
<script>
  console.log(foo) //ReferenceError
  const foo = 'bar'
</script>
<script>
  bar = 'baz' //ReferenceError
  let bar
</script>

Это связано с тем, что привязки этих переменных созданы до выполнения содержащего блока кода, но не инициализированы пока не будет проверен оператор объявления переменной. Попытка извлечь или изменить значение неинициализированной привязки всегда приводит к ReferenceError.

Теперь давайте проверим оценку const) оператора *1055* LexicalBinding оператора (variable = value пара), определяется следующим образом:

LexicalBinding : BindingIdentifier Initializer

  1. Пусть bindingId будет StringValue BindingIdentifier .
  2. Пусть lhs будет ResolveBinding ( bindingId * 1081) *).
  3. Пусть rhs будет результатом оценки Initializer .
  4. Let value be GetValue ( rhs ) .
  5. ReturnIfAbrupt ( значение ) .
  6. If IsAnonymousFunctionDefinition ( Инициализатор ) имеет значение true, тогда
    1. Пусть hasNameProperty будет HasOwnProperty ( значение , "имя").
    2. ReturnIfAbrupt ( hasNameProperty ).
    3. Если hasNameProperty равно false, выполнить SetFunctionName ( значение , bindingId ).
  7. Return InitializeReferencedBinding ( lhs , value ) .

Не вдаваясь в детали, я бы хотел выделить самые важные вещи:

  • BindingIdentifier - это имя переменной
  • Initializer - это значение, которое ему присваивается
  • ReturnIfAbrupt() - абстрактный алгоритм, который возвращается из вызывающий с его аргументом, если его аргумент является записью завершения, которая представляет внезапное завершение (например, выброшенное исключение)
  • InitializeReferencedBinding() инициализирует данное связывание

Проблема возникает, когда исключение, выданное во время оценки Initializer.

Когда это произойдет, это:

GetValue(rhs)

... вернет внезапное завершение, поэтому следующая строка:

ReturnIfAbrupt(value)

... возвращается из оператора let (или const) с неожиданной записью о завершении (т. Е. Повторные броски исключение), поэтому строка:

InitializeReferencedBinding(lhs, value)

... не будет работать вообще, поэтому привязка переменной остается неинициализированной и продолжает выдавать RefereceError s , когда вы попытаться найти его или присвоить ему.

Сообщение об этих ошибках (foo is not defined) еще более запутанно и неуместно, но это зависит от реализации, поэтому я не могу рассуждать об этом; однако, вероятно, это из-за другого необработанного случая.

2 голосов
/ 16 января 2020

let foo = undefined; это объявленная неопределенная переменная, если вы используете ее где-то, вы получите foo is undefined, если вы попытаетесь объявить ее снова, вы получите ошибку SyntaxError: redeclaration, некоторые функции возвращают неопределенное значение в случае сбоя, переменная вы используете для хранения возвращаемое значение будет объявлено и неопределенным в то же время. в этом примере вы можете использовать foo, но вы не можете повторно объявить его, например let foo = undefined; foo = 5;

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...