web-dev-qa-db-fra.com

Pourquoi ces extraits de code JavaScript se comportent-ils différemment même s'ils rencontrent tous deux une erreur?

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
107
Kevin Askin

En fait, si vous lisez correctement le message d'erreur, les cas 1 et 2 génèrent des erreurs différentes.

Cas a.x.y:

Impossible de définir la propriété 'y' de non défini

Cas a.x.y.z:

Impossible de lire la propriété 'y' de non défini

Je suppose qu'il est préférable de le décrire par une exécution étape par étape dans un anglais facile.

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

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

Dans les commentaires, Solomon Tam trouvé cette documentation ECMA sur l'opération d'affectation .

151
yqlim

L'ordre des opérations est plus clair lorsque vous exploitez l'opérateur virgule dans la notation entre crochets pour voir quelles parties sont exécutées lorsque:

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

En regardant le spec :

La production AssignmentExpression : LeftHandSideExpression = AssignmentExpression Est évaluée comme suit:

  1. Soit lref le résultat de l'évaluation de LeftHandSideExpression.

  2. Soit rref le résultat de l'évaluation de AssignmentExpression.

  3. Soit rval GetValue(rref).

  4. Lance une exception SyntaxError si ... (non pertinent)

  5. Appelez PutValue(lref, rval).

PutValue est ce qui jette le TypeError:

  1. Soit O soit ToObject(base).

  2. Si le résultat de l'appel de la méthode interne [[CanPut]] De O avec l'argument P est faux, alors

    une. Si Throw est vrai, lancez une exception TypeError.

Rien ne peut être attribué à une propriété de undefined - la méthode interne [[CanPut]] De undefined renverra toujours false.

En d'autres termes: l'interpréteur analyse le côté gauche, puis analyse le côté droit, alors renvoie une erreur si la propriété du côté gauche ne peut pas être affectée à.

Quand tu fais

a.x.y = b.e = 1

Le côté gauche est analysé avec succès jusqu'à ce que PutValue soit appelé; le fait que la propriété .x soit évaluée à undefined n'est pris en compte qu'après l'analyse du côté droit. L'interpréteur le voit comme "Attribuer une valeur à la propriété" y "d'undefined", et l'affecter à une propriété de undefined ne lance qu'à l'intérieur de PutValue.

En revanche:

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

L'interpréteur n'arrive jamais au point où il essaie d'affecter à la propriété z, car il doit d'abord résoudre a.x.y En une valeur. Si a.x.y Était résolu en une valeur (même en undefined), ce serait OK - une erreur serait lancée à l'intérieur de PutValue comme ci-dessus. Mais accèsa.x.y Génère une erreur, car la propriété y n'est pas accessible sur undefined.

57
CertainPerformance

Considérez le code suivant:

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

Les grandes lignes des étapes nécessaires à l'exécution du code sont les suivantes ref:

  1. Évaluez le côté gauche. Deux choses à garder à l'esprit:
    • L'évaluation d'une expression n'est pas la même chose que l'obtention de la valeur d'une expression.
    • Évaluation d'un accesseur de propriété ref par exemple. a.x.y renvoie une référence ref composé de la valeur de base a.x (non défini) et nom référencé (y).
  2. Évaluez le côté droit.
  3. Obtenez la valeur du résultat obtenu à l'étape 2.
  4. Définissez la valeur de la référence obtenue à l'étape 1 sur la valeur obtenue à l'étape 3, c'est-à-dire définissez la propriété y de non défini sur la valeur. Ceci est censé lever une exception TypeError ref.
3
Salman A