Warum verhalten sich diese JavaScript-Schnipsel unterschiedlich, obwohl beide auf einen Fehler stoßen?

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
quelle
3
@NinaScholz: Ich verstehe nicht. Es liegt kein Syntaxfehler vor. also würde ich das annehmen b.z = 1und b.e = 1zuerst ausführen (bei gegebener Rechtsassoziativität =), dann a.x.y.z = ...ausführen und fehlschlagen; Warum besteht die bAufgabe in einem Fall, in dem anderen jedoch nicht?
Amadan
3
@NinaScholz Wir sind uns einig, dass die Immobilie am ynicht existiert a.x; aber das ist in beiden Fällen wahr. Warum verhindert es die Zuordnung auf der rechten Seite im zweiten Fall, aber nicht im ersten? Was ist anders in der Reihenfolge der Ausführung? (Ich erwähnte Syntaxfehler, weil das Timing des Syntaxfehlers sich stark von dem eines Laufzeitfehlers unterscheidet.)
Amadan
@ Amadan nach dem Ausführen von Code erhalten Sie eine Fehlermeldung, und dann verwenden Sie den Variablennamen erneut, um den Wert anzuzeigen
Code Maniac
2
Gefunden dies beschreiben, wie Javascript Prozedur
Solomon Tam
2
Aus theoretischer Sicht ist es interessant, aber dies fällt definitiv unter die Kategorie "Deshalb schreiben Sie keinen solchen Code" für unerwartetes Verhalten.
John Montgomery

Antworten:

152

Wenn Sie die Fehlermeldung richtig lesen, werfen Fall 1 und Fall 2 unterschiedliche Fehler auf.

Fall a.x.y:

Die Eigenschaft 'y' von undefined kann nicht festgelegt werden

Fall a.x.y.z:

Die Eigenschaft 'y' von undefined kann nicht gelesen werden

Ich denke, es ist am besten, es durch schrittweise Ausführung in einfachem Englisch zu beschreiben.

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

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

In den Kommentaren, Solomon Tam fanden diese ECMA Dokumentation über Zuweisungsoperation .

yqlim
quelle
57

Die Reihenfolge der Operationen ist klarer, wenn Sie den Komma-Operator in der Klammer-Notation ausnutzen, um zu sehen, welche Teile ausgeführt werden, wenn:

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

Betrachtet man die Spezifikation :

Die Produktion AssignmentExpression : LeftHandSideExpression = AssignmentExpressionwird wie folgt bewertet:

  1. Sei lref das Ergebnis der Auswertung von LeftHandSideExpression.

  2. Sei rref das Ergebnis der Auswertung von AssignmentExpression.

  3. Sei rval GetValue(rref).

  4. Eine SyntaxError-Ausnahme auslösen, wenn ... (irrelevant)

  5. Rufen Sie an PutValue(lref, rval).

PutValueist das, was das wirft TypeError:

  1. Lass O sein ToObject(base).

  2. Wenn das Ergebnis des Aufrufs der [[CanPut]]internen Methode von O mit dem Argument P falsch ist, dann

    ein. Wenn Throw true ist, wird eine TypeError-Ausnahme ausgelöst.

Einer Eigenschaft von kann nichts zugewiesen werden undefined- die [[CanPut]]interne Methode von undefinedwird immer zurückgegeben false.

Mit anderen Worten: parst der Dolmetscher auf die linke Seite, analysiert dann die rechte Seite, dann wirft einen Fehler , wenn die Eigenschaft auf der linken Seite nicht zugeordnet werden können.

Wenn Sie das tun

a.x.y = b.e = 1

Die linke Seite wird erfolgreich analysiert , bis sie PutValueaufgerufen wird. Die Tatsache, dass die .xEigenschaft bewertet wird, undefinedwird erst berücksichtigt, nachdem die rechte Seite analysiert wurde. Der Interpreter sieht es als "Weisen Sie der Eigenschaft" y "von undefined einen Wert zu und weisen Sie einer Eigenschaft zu, die undefinednur Würfe enthält PutValue.

Im Gegensatz:

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

Der Interpreter kommt nie an den Punkt, an dem er versucht, der zEigenschaft zuzuweisen , da er zuerst a.x.yin einen Wert aufgelöst werden muss. Wenn es a.x.yauf einen Wert (sogar auf undefined) aufgelöst würde, wäre es in Ordnung - ein Fehler würde PutValuewie oben hineingeworfen . Der Zugriff a.x.y löst jedoch einen Fehler aus, da auf die Eigenschaft ynicht zugegriffen werden kann undefined.

Bestimmte Leistung
quelle
20
Netter Komma-Operator-Trick - hätte nie gedacht, ihn so zu verwenden (natürlich nur zum Debuggen)!
ecraig12345
2
s / parse / evaluieren /
Bergi
3

Betrachten Sie den folgenden Code:

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

Die grobe Übersicht über die Schritte erforderlich um den Code auszuführen ist wie folgt ref :

  1. Bewerten Sie die linke Seite. Zwei Dinge zu beachten:
    • Das Auswerten eines Ausdrucks ist nicht dasselbe wie das Abrufen des Ausdruckswerts.
    • Auswerten eine Eigenschaftenaccessor ref zB a.x.yeine Referenz Ref bestehend aus Grundwert a.x(nicht definiert) und referenzierten name ( y).
  2. Bewerten Sie die rechte Seite.
  3. Ermitteln Sie den Wert des in Schritt 2 erhaltenen Ergebnisses.
  4. Setzen Sie den Wert der in Schritt 1 erhaltenen Referenz auf den in Schritt 3 erhaltenen Wert, dh setzen Sie die Eigenschaft yundefined auf den Wert. Dies soll eine TypeError-Ausnahme ref auslösen .
Salman A.
quelle