Mengapa cuplikan JavaScript ini berperilaku berbeda meskipun keduanya mengalami kesalahan?

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

Kevin Askin
sumber
3
@NinaScholz: Saya tidak mengerti. Tidak ada kesalahan sintaks; jadi saya akan berasumsi itu b.z = 1dan b.e = 1mengeksekusi pertama (mengingat asosiatif kanan =), lalu a.x.y.z = ...mengeksekusi dan gagal; mengapa btugas lulus dalam satu kasus tetapi tidak di kasus lain?
Amadan
3
@NinaScholz Kami setuju bahwa properti ytidak ada di a.x; tapi itu benar dalam kedua kasus. Mengapa ini mencegah penugasan sisi kanan dalam kasus kedua tetapi tidak yang pertama? Apa yang berbeda dalam urutan eksekusi? (Saya menyebutkan kesalahan sintaks karena waktu pada kesalahan sintaks sangat berbeda dari kesalahan runtime.)
Amadan
@Amadan setelah menjalankan kode Anda akan mendapatkan kesalahan, dan kemudian gunakan dari ketik nama variabel lagi untuk melihat nilai
Kode Maniac
2
Ditemukan ini menjelaskan bagaimana Javascript melanjutkan operasi penugasan ecma-international.org/ecma-262/5.1/#sec-11.13
Solomon Tam
2
Ini menarik dari perspektif teoretis, tetapi ini jelas termasuk dalam kategori "inilah mengapa Anda tidak menulis kode seperti itu" dari perilaku yang tidak terduga.
John Montgomery

Jawaban:

152

Sebenarnya, jika Anda membaca pesan kesalahan dengan benar, kasus 1 dan kasus 2 menimbulkan kesalahan yang berbeda.

Kasus a.x.y:

Tak dapat menyetel properti 'y' dari tak terdefinisi

Kasus a.x.y.z:

Tak dapat membaca properti 'y' dari tidak ditentukan

Saya rasa yang terbaik adalah mendeskripsikannya dengan eksekusi langkah demi langkah dalam bahasa Inggris yang mudah.

Kasus 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)
}

Kasus 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)
}

Dalam komentar, Solomon Tam menemukan dokumentasi ECMA ini tentang operasi penugasan .

yqlim
sumber
57

Urutan operasi lebih jelas saat Anda mengeksploitasi operator koma di dalam tanda kurung siku untuk melihat bagian mana yang dieksekusi ketika:

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

Melihat speknya :

Produksi AssignmentExpression : LeftHandSideExpression = AssignmentExpressiondievaluasi sebagai berikut:

  1. Misalkan lref menjadi hasil evaluasi LeftHandSideExpression.

  2. Misalkan rref adalah hasil evaluasi AssignmentExpression.

  3. Biarkan rval menjadi GetValue(rref).

  4. Lemparkan pengecualian SyntaxError jika ... (tidak relevan)

  5. Panggil PutValue(lref, rval).

PutValueadalah apa yang melempar TypeError:

  1. Biarkan O menjadi ToObject(base).

  2. Jika hasil pemanggilan [[CanPut]]metode internal O dengan argumen P salah, maka

    Sebuah. Jika Throw benar, maka lemparkan pengecualian TypeError.

Tidak ada yang dapat ditetapkan ke properti undefined- [[CanPut]]metode internal undefinedakan selalu kembali false.

Dengan kata lain: interpreter mengurai sisi kiri, lalu mengurai sisi kanan, lalu melontarkan kesalahan jika properti di sisi kiri tidak dapat ditetapkan.

Saat kamu melakukan

a.x.y = b.e = 1

Sisi kiri berhasil diurai hingga PutValuedipanggil; fakta bahwa .xproperti mengevaluasi ke undefinedtidak dipertimbangkan sampai sisi kanan diuraikan. Penerjemah melihatnya sebagai "Tetapkan beberapa nilai ke properti" y "dari tidak terdefinisi", dan menetapkan ke properti undefinedhanya lemparan ke dalam PutValue.

Sebaliknya:

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

Penerjemah tidak pernah sampai pada titik di mana ia mencoba untuk menetapkan ke zproperti, karena ia harus menetapkan nilai terlebih dahulu a.x.y. Jika a.x.ydiselesaikan menjadi nilai (bahkan sampai undefined), itu akan baik-baik saja - kesalahan akan dilemparkan ke dalam PutValueseperti di atas. Tetapi mengakses a.x.y melontarkan kesalahan, karena properti ytidak dapat diakses undefined.

Kinerja tertentu
sumber
20
Trik operator koma yang bagus - tidak pernah terpikir untuk menggunakannya seperti itu (hanya untuk debugging, tentu saja)!
ecraig12345
2
s / parse / evalu /
Bergi
3

Perhatikan kode berikut:

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

Garis besar langkah-langkah kasar yang diperlukan untuk mengeksekusi kode adalah sebagai berikut ref :

  1. Evaluasi sisi kiri. Dua hal yang perlu diperhatikan:
    • Mengevaluasi ekspresi tidak sama dengan mendapatkan nilai ekspresi.
    • Mengevaluasi properti accessor ref misalnya a.x.ymengembalikan referensi ref terdiri dari nilai dasar a.x(terdefinisi) dan nama direferensikan ( y).
  2. Evaluasi sisi kanan.
  3. Dapatkan nilai hasil yang diperoleh pada langkah 2.
  4. Setel nilai referensi yang diperoleh pada langkah 1 ke nilai yang diperoleh pada langkah 3 yaitu set properti ytidak terdefinisi ke nilai. Ini seharusnya memunculkan ref pengecualian TypeError .
Salman A
sumber