Почему эти фрагменты JavaScript ведут себя по-разному, хотя они оба сталкиваются с ошибкой? - PullRequest
0 голосов
/ 12 февраля 2019

var a = {}
var b = {}

try{
  a.x.y = b.e = 1 // Uncaught TypeError: Cannot set property 'y' of undefined
} catch(err) {
  console.error(err);
}
console.log(b.e) // 1

var a = {}
var b = {}

try {
  a.x.y.z = b.e = 1 // Uncaught TypeError: Cannot read property 'y' of undefined
} catch(err) {
  console.error(err);
}

console.log(b.e) // undefined

Ответы [ 3 ]

0 голосов
/ 12 февраля 2019

Порядок операций более понятен, когда вы используете оператор запятой внутри скобки, чтобы увидеть, какие части выполняются, когда:

var a = {}
var b = {}

try{
 // Uncaught TypeError: Cannot set property 'y' of undefined
  a
    [console.log('x'), 'x']
    [console.log('y'), 'y']
    = (console.log('right hand side'), b.e = 1);
} catch(err) {
  console.error(err);
}
console.log(b.e) // 1

var a = {}
var b = {}

try {
  // Uncaught TypeError: Cannot read property 'y' of undefined
  a
    [console.log('x'), 'x']
    [console.log('y'), 'y']
    [console.log('z'), 'z']
    = (console.log('right hand side'), b.e = 1);
} catch(err) {
  console.error(err);
}

console.log(b.e) // undefined

Глядя на спецификацию :

Производство AssignmentExpression : LeftHandSideExpression = AssignmentExpression оценивается следующим образом:

  1. Пусть lref будет результатом вычисления LeftHandSideExpression.

  2. Пусть rref будет результатом оценки AssignmentExpression.

  3. Пусть rval будет GetValue(rref).

  4. Сгенерировать исключение SyntaxError, если ... (не имеет значения)

  5. Вызов PutValue(lref, rval).

PutValue - это то, что бросает TypeError:

  1. Пусть O будет ToObject(base).

  2. Если результатом вызова внутреннего метода O [[CanPut]] с аргументом P является false, то

    a.Если Throw имеет значение true, тогда выдается исключение TypeError.

Ничто не может быть назначено свойству undefined - внутренний метод [[CanPut]] undefined всегда будетreturn false.

Другими словами: интерпретатор анализирует левую часть, затем анализирует правую часть, затем выдает ошибку, если свойство слевасторона не может быть назначена.

Когда вы делаете

a.x.y = b.e = 1

Левая сторона - успешно проанализирована до вызова PutValue;тот факт, что свойство .x оценивается как undefined, не учитывается до тех пор, пока не будет проанализирована правая часть.Интерпретатор видит это как «Присвоить какое-либо значение свойству« y »неопределенного значения», а присвоение свойству undefined только бросает внутрь PutValue.

Для сравнения:

a.x.y.z = b.e = 1

Интерпретатор никогда не достигает точки, в которой он пытается присвоить свойству z, потому что сначала он должен разрешить a.x.y в значение.Если a.x.y разрешить до значения (даже до undefined), все будет в порядке - в PutValue будет выдано сообщение об ошибке, как указано выше.Но доступ к a.x.y выдает ошибку, потому что свойство y недоступно в undefined.

0 голосов
/ 13 февраля 2019

Рассмотрим следующий код:

var a = {};
a.x.y = console.log("evaluating right hand side"), 1;

Примерное описание шагов, необходимых для выполнения кода, выглядит следующим образом: ref :

  1. Оцените левую сторону.Необходимо помнить две вещи:
    • Оценка выражения - это не то же самое, что получение значения выражения.
    • Оценка средства доступа к свойству ref например, a.x.y возвращает ссылку ref , состоящую из базового значения a.x (не определено) и ссылочного имени (y).
  2. Оцените правую часть.
  3. Получите значение результата, полученного на шаге 2.
  4. Установите значение эталона, полученного на шаге 1, равным значению, полученному на шаге 3, т.е. установите свойство y от неопределенного значения.Это должно вызвать исключение TypeError ref .
0 голосов
/ 12 февраля 2019

На самом деле, если вы правильно прочитали сообщение об ошибке, случай 1 и случай 2 выдают разные ошибки.

Случай a.x.y:

Невозможно установить свойство 'y' не определено

регистр a.x.y.z:

Невозможно прочитать свойство 'y' не определено

Полагаю, лучше описать это пошаговым исполнением на простом английском языке.

Дело 1

// 1. Declare variable `a`
// 2. Define variable `a` as {}
var a = {}

// 1. Declare variable `b`
// 2. Define variable `b` as {}
var b = {}

try {

  /**
   *  1. Read `a`, gets {}
   *  2. Read `a.x`, gets undefined
   *  3. Read `b`, gets {}
   *  4. Set `b.z` to 1, returns 1
   *  5. Set `a.x.y` to return value of `b.z = 1`
   *  6. Throws "Cannot **set** property 'y' of undefined"
   */
  a.x.y = b.z = 1
  
} catch(e){
  console.error(e.message)
} finally {
  console.log(b.z)
}

Дело 2

// 1. Declare variable `a`
// 2. Define variable `a` as {}
var a = {}

// 1. Declare variable `b`
// 2. Define variable `b` as {}
var b = {}

try {

  /**
   *  1. Read `a`, gets {}
   *  2. Read `a.x`, gets undefined
   *  3. Read `a.x.y`, throws "Cannot **read** property 'y' of undefined".
   */
  a.x.y.z = b.z = 1
  
} catch(e){
  console.error(e.message)
} finally {
  console.log(b.z)
}

В комментариях Соломон Тэм найдено эта документация ECMA об операции назначения .

...