¿Por qué estos fragmentos de JavaScript se comportan de manera diferente a pesar de que ambos encuentran un error?
operators order-of-execution (3)
Considere el siguiente código:
var a = {};
a.x.y = console.log("evaluating right hand side"), 1;
El resumen aproximado de los pasos necesarios para ejecutar el código es el siguiente ref :
- Evaluar el lado izquierdo. Dos cosas a tener en cuenta:
- Evaluar el lado derecho.
- Obtenga el valor del resultado obtenido en el paso 2.
-
Establezca el valor de la referencia obtenida en el paso 1 al valor obtenido en el paso 3, es decir, establezca la propiedad
y
de indefinido al valor. Esto se supone para lanzar una excepción TypeError ref .
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
El orden de las operaciones es más claro cuando explota el operador de coma dentro de la notación de corchetes para ver qué partes se ejecutan cuando:
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
Mirando la spec :
La producción
AssignmentExpression : LeftHandSideExpression = AssignmentExpression
se evalúa de la siguiente manera:
Sea lref el resultado de evaluar LeftHandSideExpression.
Sea rref el resultado de evaluar AssignmentExpression.
Deje rval ser
GetValue(rref)
.Lanzar una excepción SyntaxError si ... (irrelevante)
Llamar a
PutValue(lref, rval)
.
PutValue
es lo que lanza el
TypeError
:
Sea O sea
ToObject(base)
.Si el resultado de llamar al
[[CanPut]]
método interno de O con el argumento P es falso, entoncesa. Si Throw es verdadero, lanza una excepción TypeError.
No se puede asignar nada a una propiedad de
undefined
: el método interno de
[[CanPut]]
de
undefined
siempre devolverá
false
.
En otras palabras: el intérprete analiza el lado izquierdo, luego analiza el lado derecho y luego arroja un error si la propiedad del lado izquierdo no puede asignarse.
Cuando tu lo hagas
a.x.y = b.e = 1
El lado izquierdo se
analiza correctamente
hasta que se llama a
PutValue
;
el hecho de que la propiedad
.x
evalúe como
undefined
no se considera hasta que se analiza el lado derecho.
El intérprete lo ve como "Asignar algún valor a la propiedad" y "de indefinido", y asignarlo a una propiedad de
undefined
solo se lanza dentro de
PutValue
.
A diferencia de:
a.x.y.z = b.e = 1
El intérprete nunca llega al punto en el que intenta asignar a la propiedad
z
, porque primero debe resolver
axy
a un valor.
Si
axy
resolviera a un valor (incluso a
undefined
), estaría bien, se arrojaría un error dentro de
PutValue
como arriba.
Pero
acceder a
axy
un error, porque no se puede acceder a la propiedad
y
en
undefined
.
En realidad, si lees el mensaje de error correctamente, el caso 1 y el caso 2 producen errores diferentes.
Caso
axy
:
No se puede establecer la propiedad ''y'' de indefinido
Caso
axyz
:
No se puede leer la propiedad ''y'' de undefined
Supongo que lo mejor es describirlo paso a paso en un inglés fácil.
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)
}
En comentarios, Solomon Tam encontró ecma-international.org/ecma-262/5.1/#sec-11.13 .