¿Por qué 0 [0] es sintácticamente válido?

119

¿Por qué esta línea es válida en javascript?

var a = 0[0];

Después de eso, aes undefined.

Michael M.
fuente
4
como true[0]o ""[0]
Hacketo
24
@CodeAngry Para ser justos, JavaScript nació dentro de HTML, y es HTML el que inició todo el asunto de "arrojame lo que quieras y trataré de darle sentido".
Niet the Dark Absol
8
@NiettheDarkAbsol Para ser justos, simplemente estás equivocado, ya que la sintaxis tiene sentido (pero no tanto). Esto es solo obtener la propiedad nombrada "0"de un new Number(0)objeto.
meandre
es una suposición falsa que a siempre estará indefinido. Es perfectamente posible 0[0]devolver un valor
Rune FS
@meandre 0["toString"]Eso es genial, gracias por señalarlo.
Jonathan

Respuestas:

169

Cuando lo haga 0[0], el intérprete de JS convertirá el primero 0en un Numberobjeto y luego intentará acceder a la [0]propiedad de ese objeto que es undefined.

No hay ningún error de sintaxis porque la sintaxis de acceso a la propiedad 0[0]está permitida por la gramática del idioma en este contexto. Esta estructura (usando términos en la gramática de Javascript) es NumericLiteral[NumericLiteral].

La parte relevante de la gramática del lenguaje de la sección A.3 de la especificación ECMAScript de ES5 es la siguiente:

Literal ::
    NullLiteral
    BooleanLiteral
    NumericLiteral
    StringLiteral
    RegularExpressionLiteral

PrimaryExpression :
    this
    Identifier
    Literal
    ArrayLiteral
    ObjectLiteral
    ( Expression )

MemberExpression :
    PrimaryExpression
    FunctionExpression
    MemberExpression [ Expression ]
    MemberExpression . IdentifierName
    new MemberExpression Arguments    

Entonces, uno puede seguir la gramática a través de esta progresión:

MemberExpression [ Expression ]
PrimaryExpression [ Expression ]
Literal [ Expression ]
NumericLiteral [ Expression ]

Y, de manera similar, también Expressionpuede eventualmente ser NumericLiteralasí después de seguir la gramática, vemos que esto está permitido:

NumericLiteral [ NumericLiteral ]

Lo que significa que 0[0]es una parte permitida de la gramática y, por lo tanto, no SyntaxError.


Luego, en tiempo de ejecución, se le permite leer una propiedad que no existe (solo se leerá como undefined) siempre que la fuente de la que está leyendo sea un objeto o tenga una conversión implícita a un objeto. Y, de hecho, un literal numérico tiene una conversión implícita a un objeto (un objeto Number).

Esta es una de esas características a menudo desconocidas de Javascript. Los tipos Number, Booleany Stringen Javascript, generalmente se almacenan internamente como primitivas (no como objetos completos). Se trata de una representación de almacenamiento compacta e inmutable (probablemente hecha de esta manera para la eficiencia de la implementación). Pero, Javascript quiere que pueda tratar estas primitivas como objetos con propiedades y métodos. Por lo tanto, si intenta acceder a una propiedad o método que no es compatible directamente con la primitiva, JavaScript coaccionará temporalmente la primitiva en un tipo de objeto apropiado con el valor establecido en el valor de la primitiva.

Cuando usa una sintaxis similar a un objeto en una primitiva como 0[0], el intérprete lo reconoce como un acceso a la propiedad en una primitiva. Su respuesta a esto es tomar la primera 0primitiva numérica y convertirla en un Numberobjeto en toda regla en el que luego puede acceder a la [0]propiedad. En este caso específico, la [0]propiedad de un objeto Number es la undefinedrazón por la que ese es el valor que obtiene 0[0].

Aquí hay un artículo sobre la conversión automática de una primitiva en un objeto con el propósito de tratar con propiedades:

La vida secreta de los primitivos de JavaScript


Aquí están las partes relevantes de la especificación ECMAScript 5.1:

9.10 CheckObjectCoercible

Lanza TypeError si el valor es undefinedo null, de lo contrario, devuelve true.

ingrese la descripción de la imagen aquí

11.2.1 Acceso a la propiedad

  1. Sea baseReference el resultado de evaluar MemberExpression.
  2. Deje que baseValue sea GetValue (baseReference).
  3. Sea propertyNameReference el resultado de evaluar Expression.
  4. Deje que propertyNameValue sea GetValue (propertyNameReference).
  5. Llame a CheckObjectCoercible (baseValue).
  6. Deje que propertyNameString sea ToString (propertyNameValue).
  7. Si la producción sintáctica que se está evaluando está contenida en el código de modo estricto, deje que estricto sea verdadero, de lo contrario, deje que estricto sea falso.
  8. Devuelve un valor de tipo Reference cuyo valor base es baseValue y cuyo nombre referenciado es propertyNameString, y cuyo indicador de modo estricto es estricto.

Una parte operativa de esta pregunta es el paso 5 anterior.

8.7.1 Obtener valor (V)

Esto describe cómo cuando el valor al que se accede es una referencia de propiedad, llama ToObject(base)para obtener la versión del objeto de cualquier primitiva.

9.9 ToObject

Esto describe cómo Boolean, Numbery las Stringprimitivas se convierten en un formulario de objeto con la propiedad interna [[PrimitiveValue]] establecida en consecuencia.


Como prueba interesante, si el código fuera así:

var x = null;
var a = x[0];

Todavía no arrojaría un SyntaxError en el tiempo de análisis, ya que esta es una sintaxis técnicamente legal, pero arrojaría un TypeError en el tiempo de ejecución cuando ejecute el código porque cuando la lógica de Accessors de propiedad anterior se aplica al valor de x, llamará CheckObjectCoercible(x)o llamará a ToObject(x)cuál ambos arrojarán un TypeError si xes nullo undefined.

jfriend00
fuente
0[1,2]también es válido, ¿qué significa? (Actualizo la pregunta)
Michael M.
Y no genera un error de sintaxis, porque acceder a las propiedades de cualquier cosa que no esté nullo undefinedesté totalmente bien, incluso si esas propiedades no existen.
user4642212
6
@Michael no es necesario actualizar. Eso es un operador de coma, así que es solo0[2]
Amit Joki
1
Operador de coma: evalúa 1 y 2 en 1,2pero devuelve 2.
user4642212
2
Esa es una respuesta tan impresionante para el matiz de una pregunta.
tbh__
20

Como la mayoría de los lenguajes de programación, JS usa una gramática para analizar su código y convertirlo en un formato ejecutable. Si no hay una regla en la gramática que se pueda aplicar a un fragmento particular de código, arroja un SyntaxError. De lo contrario, el código se considera válido, sin importar si tiene sentido o no.

Las partes relevantes de la gramática JS son

Literal :: 
   NumericLiteral
   ...

PrimaryExpression :
   Literal
   ...

MemberExpression :
   PrimaryExpression
   MemberExpression [ Expression ]
   ...

Dado que 0[0]cumple con estas reglas, se considera una expresión válida . Si es correcto (por ejemplo, no arroja un error en tiempo de ejecución) es otra historia, pero sí lo es. Así es como JS evalúa expresiones como someLiteral[someExpression]:

  1. evaluar someExpression(que puede ser complejo arbitrario)
  2. convertir el literal en un tipo de objeto correspondiente (literales numéricos => Number, cadenas => Stringetc.)
  3. llamar a la get propertyoperación en el resultado (2) con el nombre de la propiedad resultado (1)
  4. descartar resultado (2)

Entonces 0[0]se interpreta como

index = 0
temp = Number(0)
result = getproperty(temp, index) // it's undefined, but JS doesn't care
delete temp
return result

A continuación, se muestra un ejemplo de una expresión válida , pero incorrecta :

null[0]

Se analiza bien, pero en tiempo de ejecución, el intérprete falla en el paso 2 (porque nullno se puede convertir en un objeto) y arroja un error de tiempo de ejecución.

georg
fuente
1
Hay más que eso. var x = null; var a = x[0];no genera un error de sintaxis, pero arroja un TypeError en tiempo de ejecución.
jfriend00
@ jfriend00: de eso no se trataba la pregunta, pero agregó.
georg
el resultado no tiene por qué ser indefinido. Es posible llegar 0[0]a devolver un valor en lugar de indefinido
Rune FS
9

Hay situaciones en las que podría subíndice válidamente un número en Javascript:

-> 0['toString']
function toString() { [native code] }

Si bien no es evidente de inmediato por qué querría hacer esto, el subíndice en Javascript es equivalente a usar la notación con puntos (aunque la notación con puntos le limita a usar identificadores como claves).

Duncan
fuente
@AmitJoki Esto es lo mismo que (0).toString(sin llamar a la función). Es una propiedad del tipo de número.
user4642212
@AmitJoki porque responde a la pregunta '¿por qué es válida esta línea?'.
Duncan
@Duncan, pero esto es más de "qué notación entre corchetes" y supongo que OP lo sabe. El hecho de que se interprete como objeto Number y luego 0se acceda a su propiedad y como no existe, undefinedes más correcto como se explica en jfriend00.
Amit Joki
@AmitJoki es una suposición incorrecta que 0[0]devolverá undefined. Es probable que así sea, pero no tiene por qué ser así
Rune FS
9

Solo me gustaría señalar que esta sintaxis válida no es de ninguna manera exclusiva de Javascript. La mayoría de los lenguajes tendrán un error de tiempo de ejecución o un error de tipo, pero eso no es lo mismo que un error de sintaxis. Javascript elige devolver undefined en muchas situaciones en las que otro idioma podría generar una excepción, incluso cuando se subíndice un objeto que no tiene una propiedad del nombre de pila.

La sintaxis no conoce el tipo de expresión (incluso una expresión simple como un literal numérico) y le permitirá aplicar cualquier operador a cualquier expresión. Por ejemplo, intentar subíndice undefinedo nullprovoca un TypeErroren Javascript. No es un error de sintaxis; si esto nunca se ejecuta (estando en el lado equivocado de una sentencia if), no causará ningún problema, mientras que un error de sintaxis, por definición, siempre se detecta en tiempo de compilación (eval, Function, etc. , todos cuentan como compilación).

Aleatorio832
fuente
8

Porque es una sintaxis válida, e incluso un código válido para ser interpretado. Puede intentar acceder a cualquier propiedad de cualquier objeto (y en este caso 0 se convertirá en un objeto Número), y le dará el valor si existe, de lo contrario indefinido. Sin embargo, intentar acceder a una propiedad de undefined no funciona, por lo que 0 [0] [0] resultaría en un error de tiempo de ejecución. Sin embargo, esto todavía se clasificaría como sintaxis válida. Hay una diferencia entre lo que es una sintaxis válida y lo que no causará errores en tiempo de ejecución / tiempo de compilación.

Clox
fuente
3

No solo la sintaxis es válida, el resultado no tiene por qué serlo undefineden la mayoría de los casos, si no en todos los casos cuerdos. JS es uno de los lenguajes orientados a objetos más puros. La mayoría de los llamados lenguajes OO están orientados a clases, en el sentido de que no se puede cambiar la forma (está ligada a la clase) del objeto una vez creado, solo el estado del objeto. En JS puede cambiar el estado y la forma del objeto y esto lo hace con más frecuencia de lo que cree. Esta capacidad lo convierte en un código bastante oscuro, si lo usa incorrectamente. Los números son inmutables, por lo que no puede cambiar el objeto en sí, ni su estado ni su forma, por lo que podría hacerlo.

0[0] = 1;

que es una expresión de asignación válida que devuelve 1 pero en realidad no asigna nada, el número 0es inmutable. Lo que en sí mismo es algo extraño. Puede tener una expresión de asignación válida y correcta (ejecutable), que no asigna nada (*). Sin embargo, el tipo de numeral es un objeto mutable por lo que puede mutar el tipo, y los cambios caerán en cascada por la cadena del prototipo.

Number[0] = 1;
//print 1 to the console
console.log(0[0]);
//will also print 1 to the console because all integers have the same type
console.log(1[0]); 

por supuesto, está muy lejos de la categoría de uso sano, pero el lenguaje está especificado para permitir esto porque en otros escenarios, extender las capacidades de los objetos tiene mucho sentido. Así es como los complementos de jQuery se enganchan en el objeto jQuery para dar un ejemplo.

(*) En realidad, asigna el valor 1 a la propiedad de un objeto, sin embargo, no hay forma de que pueda hacer referencia a ese objeto (transitorio) y, por lo tanto, se recopilará en el paso nexx GC

Rune FS
fuente
3

En JavaScript, todo es objeto, por lo que cuando el intérprete lo analiza, trata 0 como un objeto e intenta devolver 0 como una propiedad. Lo mismo sucede cuando intenta acceder al elemento 0 de verdadero o "" (cadena vacía).

Incluso si establece 0 [0] = 1, establecerá la propiedad y su valor en la memoria, pero mientras accede a 0 se trata como un número (no se confunda entre tratar como Objeto y número aquí).

Laxmikant Dange
fuente