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

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
la source
3
@NinaScholz: Je ne comprends pas. Il n'y a pas d'erreur de syntaxe; donc je supposerais que b.z = 1et b.e = 1exécuter en premier (étant donné l'associativité à droite =), puis a.x.y.z = ...exécuter et échouer; pourquoi l' baffectation passe-t-elle dans un cas mais pas dans l'autre?
Amadan le
3
@NinaScholz Nous convenons que la propriété yn'existe pas sur a.x; mais c'est vrai dans les deux cas. Pourquoi empêche-t-il l'affectation du côté droit dans le second cas mais pas dans le premier? Qu'est-ce qui est différent dans l'ordre d'exécution? (J'ai mentionné une erreur de syntaxe car le timing de l'erreur de syntaxe est très différent de celui d'une erreur d'exécution.)
Amadan
@Amadan après l'exécution du code, vous obtiendrez une erreur, puis utilisez à nouveau le nom de la variable pour voir la valeur
Code Maniac
2
Trouvé ceci décrivez comment Javascript procède à l'opération d'affectation ecma-international.org/ecma-262/5.1/#sec-11.13
Solomon Tam
2
C'est intéressant d'un point de vue théorique, mais cela relève certainement de la catégorie «c'est pourquoi vous n'écrivez pas de code comme ça» de comportement inattendu.
John Montgomery

Réponses:

152

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» sur undefined

Cas a.x.y.z:

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

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

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 a trouvé cette documentation ECMA sur l'opération d'affectation .

yqlim
la source
57

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 les spécifications :

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

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

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

  3. Soit rval GetValue(rref).

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

  5. Appelez PutValue(lref, rval).

PutValueest ce qui jette le TypeError:

  1. Soit O ToObject(base).

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

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

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

En d'autres termes: l'interpréteur analyse le côté gauche, puis analyse le côté droit, puis renvoie une erreur si la propriété sur le 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 qu'il PutValuesoit appelé; le fait que la .xpropriété soit évaluée undefinedn'est pas pris en compte tant que le membre droit n'a pas été analysé. L'interpréteur le voit comme "Attribuer une valeur à la propriété" y "d'undefined", et l'assigner à une propriété de undefinedne jette qu'à l'intérieur PutValue.

En revanche:

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

L'interpréteur n'atteint jamais le point où il essaie d'attribuer à la zpropriété, car il doit d'abord se résoudre a.x.yà une valeur. Si a.x.yrésolu à une valeur (même à undefined), ce serait OK - une erreur serait jetée à l'intérieur PutValuecomme ci-dessus. Mais l' accès a.x.y génère une erreur, car la propriété yn'est pas accessible sur undefined.

CertainPerformance
la source
20
Belle astuce d'opérateur virgule - je n'ai jamais pensé à l'utiliser de cette façon (pour le débogage uniquement, bien sûr)!
ecraig12345
2
s / parse / evaluer /
Bergi
3

Considérez le code suivant:

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

Les grandes lignes des étapes requises pour exécuter le code sont les suivantes réf :

  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'expression.
    • L'évaluation d'un accesseur de propriété ref, par exemple, a.x.yretourne une référence ref composée d'une valeur de base a.x(non définie) et d'un 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é yde indéfini sur la valeur. Ceci est censé lancer une exception TypeError ref .
Salman A
la source