Perché questi snippet di JavaScript si comportano in modo diverso anche se entrambi riscontrano un errore?


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: non capisco. Non ci sono errori di sintassi; quindi lo presumo b.z = 1ed b.e = 1eseguo prima (data l'associatività a destra su =), poi a.x.y.z = ...esegua e fallisca; perché l' bincarico passa in un caso ma non nell'altro?
Amadan

3
@NinaScholz Siamo d'accordo che la proprietà ynon esiste su a.x; ma questo è vero in entrambi i casi. Perché impedisce l'assegnazione del lato destro nel secondo caso ma non nel primo? Cosa c'è di diverso nell'ordine di esecuzione? (Ho menzionato l'errore di sintassi perché il tempo sull'errore di sintassi è molto diverso da quello di un errore di runtime.)
Amadan

@Amadan dopo aver eseguito il codice riceverai un errore, quindi usa di nuovo il nome della variabile per vedere il valore
Code Maniac

2
Trovato questo descrive come Javascript procede all'operazione di assegnazione ecma-international.org/ecma-262/5.1/#sec-11.13
Solomon Tam

2
È interessante da un punto di vista teorico, ma rientra sicuramente nella categoria "questo è il motivo per cui non scrivi codice del genere" di comportamento inaspettato.
John Montgomery

Risposte:


152

In realtà, se leggi correttamente il messaggio di errore, il caso 1 e il caso 2 generano errori diversi.

Caso a.x.y:

Impossibile impostare la proprietà "y" di undefined

Caso a.x.y.z:

Impossibile leggere la proprietà "y" di undefined

Immagino sia meglio descriverlo passo dopo passo in un inglese facile.

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

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

Nei commenti, Solomon Tam ha trovato questa documentazione ECMA sull'operazione di assegnazione .


57

L'ordine delle operazioni è più chiaro quando si sfrutta l'operatore virgola all'interno della notazione tra parentesi per vedere quali parti vengono eseguite quando:

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

Guardando le specifiche :

La produzione AssignmentExpression : LeftHandSideExpression = AssignmentExpressionviene valutata come segue:

  1. Sia lref il risultato della valutazione di LeftHandSideExpression.

  2. Sia rref il risultato della valutazione di AssignmentExpression.

  3. Sia rval GetValue(rref).

  4. Genera un'eccezione SyntaxError se ... (irrilevante)

  5. Chiama PutValue(lref, rval).

PutValueè ciò che genera TypeError:

  1. Lascia che O sia ToObject(base).

  2. Se il risultato della chiamata al [[CanPut]]metodo interno di O con argomento P è falso, allora

    un. Se Throw è true, genera un'eccezione TypeError.

Niente può essere assegnato a una proprietà di undefined- il [[CanPut]]metodo interno di undefinedtornerà sempre false.

In altre parole: l'interprete analizza il lato sinistro, quindi analizza il lato destro, quindi genera un errore se la proprietà sul lato sinistro non può essere assegnata.

Quando lo fai

a.x.y = b.e = 1

Il lato sinistro viene analizzato con successo finché non PutValueviene chiamato; il fatto che la .xproprietà restituisca undefinednon viene considerato fino a quando non viene analizzato il lato destro. L'interprete lo vede come "Assegna un valore alla proprietà" y "di undefined" e l'assegnazione a una proprietà di undefinedsoli lanci all'interno PutValue.

In contrasto:

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

L'interprete non arriva mai al punto in cui cerca di assegnare alla zproprietà, perché prima deve risolversi a.x.yin un valore. Se a.x.yrisolto in un valore (anche in undefined), sarebbe OK - un errore verrebbe generato all'interno PutValuecome sopra. Ma l' accesso a.x.y genera un errore, perché ynon è possibile accedere alla proprietà undefined.


20
Bel trucco dell'operatore virgola - non avrei mai pensato di usarlo in questo modo (solo per il debug, ovviamente)!
ecraig12345

2
s / analizza / valuta /
Bergi

3

Considera il codice seguente:

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

Lo schema approssimativo dei passaggi necessari per eseguire il codice è il seguente rif :

  1. Valuta il lato sinistro. Due cose da tenere a mente:
    • Valutare un'espressione non è la stessa cosa che ottenere il valore dell'espressione.
    • Valutare una proprietà di accesso ref esempio a.x.yrestituisce un riferimento Rif costituito da valore base a.x(undefined) e il nome del riferimento ( y).
  2. Valuta il lato destro.
  3. Ottieni il valore del risultato ottenuto nel passaggio 2.
  4. Impostare il valore del riferimento ottenuto nel passaggio 1 al valore ottenuto nel passaggio 3, ovvero impostare la proprietà ydi undefined al valore. Questo dovrebbe generare un'eccezione TypeError ref .
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.