Чому ці фрагменти JavaScript поводяться по-різному, хоча вони обидва зустрічаються з помилкою?


107

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
@NinaScholz: Я не розумію. Синтаксичної помилки немає; тому я б припустив, що b.z = 1і b.e = 1виконати спочатку (з урахуванням права асоціативності на =), а потім a.x.y.z = ...виконати і відмовити; чому bприсвоєння проходить в одному випадку, а не в іншому?
Амадан

3
@NinaScholz Ми погоджуємось, що майно yне існує a.x; але це вірно в обох випадках. Чому це перешкоджає призначенню правого боку у другому випадку, а не в першому? Чим відрізняється порядок виконання? (Я згадав про синтаксичну помилку, оскільки час
синхросистемної

@Amadan після запуску коду ви отримаєте помилку, а потім використайте, ніж знову введіть ім’я змінної, щоб побачити значення
Code Maniac

2
Виявив це опис того, як операція присвоєння JavaScript триває ecma-international.org/ecma-262/5.1/#sec-11.13
Соломон Там,

2
Це цікаво з теоретичної точки зору, але це, безумовно, підпадає під категорію "ось чому ви не пишете такий код", як несподівана поведінка.
Джон Монтгомері

Відповіді:


152

Насправді, якщо ви правильно прочитали повідомлення про помилку, у випадку 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 щодо операції по призначенню .


57

Порядок операцій чіткіший, коли ви використовуєте оператор комами всередині позначень дужки, щоб побачити, які частини виконуються, коли:

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. Нехай О буде ToObject(base).

  2. Якщо результат виклику [[CanPut]]внутрішнього методу O аргументом Р помилковим, то

    а. Якщо Throw правда, то киньте виняток TypeError.

Нічого не можна присвоїти властивості undefined- [[CanPut]]внутрішній метод undefinedволі завжди повернеться 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.


20
Приємний трюк оператора кома - ніколи не думав використовувати його таким чином (звичайно, лише для налагодження)!
ecraig12345

2
s / синтаксичного аналізу / оцінки /
Берги

3

Розглянемо наступний код:

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

Груба схема кроків , необхідних для виконання коду виглядає наступним чином вих :

  1. Оцініть ліву частину. Слід пам’ятати дві речі:
    • Оцінка виразу - це не те саме, що отримання значення виразу.
    • Оцінка нерухомості аксессор вих наприклад a.x.yповертає посилання реф , що складається з базового значення a.x(не визначене) і посилається ім'я ( y).
  2. Оцініть праву частину.
  3. Отримайте значення результату, отриманого на кроці 2.
  4. Встановіть значення посилання, отриманого на кроці 1, на значення, отримане на кроці 3, тобто встановіть властивість yневизначеного значення. Це повинно кинути виняток TypeError ref .
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.