Her ikisi de bir hatayla karşılaşsalar bile bu JavaScript parçacıkları neden farklı davranıyor?


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: Anlamıyorum. Sözdizimi hatası yok; bu yüzden bunu varsayar b.z = 1ve b.e = 1önce çalıştırırdım (doğru ilişkisellik verildiğinde =), sonra a.x.y.z = ...çalıştırır ve başarısız olurum ; Neden bbir durumda atama geçiyor, diğerinde değil?
Amadan

3
@NinaScholz Biz mülkiyet konusunda hemfikir ymevcut değil a.x; ama bu her iki durumda da doğrudur. Neden ikinci durumda sağ taraf atamayı engellerken ilk durumda engellemiyor? Yürütme sırasındaki farklı olan nedir? (Sözdizimi hatasındaki zamanlama, çalışma zamanı hatasından çok farklı olduğu için Sözdizimi hatasından bahsetmiştim.)
Amadan

@Amadan sonra kodu çalıştırdıktan sonra hata alırsınız ve daha sonra değeri görmek için değişken adını tekrar yazın
Code Maniac

2
Bu, Javascript'in atama işlemini nasıl
Solomon Tam

2
Teorik açıdan ilginç, ancak bu kesinlikle beklenmedik davranışların "bu yüzden böyle kod yazmıyorsun" kategorisine giriyor.
John Montgomery

Yanıtlar:


152

Aslında, hata mesajını doğru okursanız, durum 1 ve durum 2 farklı hatalar atar.

Vaka a.x.y:

Can not set tanımsız malı 'y'

Vaka a.x.y.z:

Can okumak tanımsız malı 'y'

Sanırım bunu kolay bir İngilizce ile adım adım uygulama yoluyla tanımlamak en iyisidir.

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

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

Solomon Tam yorumlarda, atama işlemi ile ilgili bu ECMA belgesini buldu .


57

Aşağıdaki durumlarda hangi parçaların yürütüldüğünü görmek için köşeli parantez içinde virgül operatörünü kullandığınızda işlem sırası daha nettir:

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

Spesifikasyona bakıldığında :

Üretim AssignmentExpression : LeftHandSideExpression = AssignmentExpressionşu şekilde değerlendirilir:

  1. Lref, LeftHandSideExpression'u değerlendirmenin sonucu olsun.

  2. Rref, AssignmentExpression'u değerlendirmenin sonucu olsun.

  3. Rval olsun GetValue(rref).

  4. SyntaxError istisnası at, eğer ... (ilgisiz)

  5. Çağrı PutValue(lref, rval).

PutValueatan şey TypeError:

  1. O olsun ToObject(base).

  2. [[CanPut]]O'nun dahili yöntemini P bağımsız değişkeniyle çağırmanın sonucu yanlışsa, o zaman

    a. Throw doğruysa, bir TypeError istisnası atın.

Bir özelliğine hiçbir şey atanamaz undefined- [[CanPut]]iç yöntemi undefinedher zaman dönecektir false.

Başka bir deyişle: yorumlayıcı sol tarafı ayrıştırır, ardından sağ tarafı ayrıştırır, ardından sol taraftaki özellik atanamazsa bir hata atar.

Ne zaman yaparsan

a.x.y = b.e = 1

Sol taraf , çağrılana kadar başarıyla ayrıştırılırPutValue ; .xözelliğin olarak değerlendirildiği gerçeği undefined, sağ taraf çözümlenene kadar dikkate alınmaz. Yorumlayıcı bunu "tanımsız" y "özelliğine bir değer ata" olarak görür ve undefinedyalnızca içeriye atılan bir özelliği atar PutValue.

Tersine:

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

Yorumlayıcı, zözelliğe atamaya çalıştığı noktaya asla gelmez , çünkü önce a.x.ybir değere çözümlemesi gerekir . Eğer a.x.y(hatta bir değere çözüldü undefined), Tamam olurdu - bir hata içini atılmış olacağını PutValueyukarıda gibi. Ancak erişim a.x.y , özelliğe yerişim sağlanamadığı için bir hata verir undefined.


20
Güzel virgül operatörü numarası - asla bu şekilde kullanmayı düşünmedim (tabii ki sadece hata ayıklamak için)!
ecraig12345

2
s / parse /
eval

3

Aşağıdaki kodu göz önünde bulundurun:

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

Kodu çalıştırmak için gereken adımların ana hatları aşağıdaki gibidir : ref :

  1. Sol tarafı değerlendirin. Unutulmaması gereken iki nokta:
    • Bir ifadeyi değerlendirmek, ifadenin değerini almakla aynı şey değildir.
    • Bir özellik erişimcisinin ref değerlendirilmesi, örneğin temel değerden (tanımsız) ve başvurulan addan ( ) oluşan a.x.ybir referans ref verir .a.xy
  2. Sağ tarafı değerlendirin.
  3. 2. adımda elde edilen sonucun değerini alın.
  4. 1. adımda elde edilen referansın değerini 3. adımda elde edilen değere ayarlayın y, yani tanımsız özelliğini değere ayarlayın. Bunun bir TypeError istisnası ref atması gerekir .
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.