Pourquoi 0 [0] est-il syntaxiquement valide?


119

Pourquoi cette ligne est-elle valide en javascript?

var a = 0[0];

Après cela, ac'est undefined.


4
as true[0]ou ""[0]
Hacketo

24
@CodeAngry Pour être juste, JavaScript est né au sein du HTML, et c'est HTML qui a commencé le tout "lancez-moi tout ce que vous voulez et j'essaierai de donner un sens".
Niet the Dark Absol

8
@NiettheDarkAbsol Pour être honnête , vous êtes tout simplement faux que la syntaxe ne du sens (mais pas tant). Il s'agit simplement d'obtenir le nom "0"de la propriété d'un new Number(0)objet.
Meandre

c'est une fausse hypothèse selon laquelle a sera toujours indéfini. Il est parfaitement possible d'avoir 0[0]une valeur de retour
Rune FS

@meandre 0["toString"]C'est génial, merci de l'avoir signalé.
Jonathan

Réponses:


169

Lorsque vous le faites 0[0], l'interpréteur JS transformera le premier 0en Numberobjet, puis essaiera d'accéder à la [0]propriété de cet objet qui est undefined.

Il n'y a pas d'erreur de syntaxe car la syntaxe d'accès aux propriétés 0[0]est autorisée par la grammaire du langage dans ce contexte. Cette structure (utilisant des termes dans la grammaire Javascript) est NumericLiteral[NumericLiteral].

La partie pertinente de la grammaire du langage de la section A.3 de la spécification ES5 ECMAScript est la suivante:

Literal ::
    NullLiteral
    BooleanLiteral
    NumericLiteral
    StringLiteral
    RegularExpressionLiteral

PrimaryExpression :
    this
    Identifier
    Literal
    ArrayLiteral
    ObjectLiteral
    ( Expression )

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

Ainsi, on peut suivre la grammaire à travers cette progression:

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

Et, de même Expressionpeut aussi éventuellement l'être NumericLiteralaprès avoir suivi la grammaire, nous voyons que cela est autorisé:

NumericLiteral [ NumericLiteral ]

Ce qui signifie que 0[0]c'est une partie autorisée de la grammaire et donc pas de SyntaxError.


Ensuite, au moment de l'exécution, vous êtes autorisé à lire une propriété qui n'existe pas (elle sera simplement lue en tant que undefined) tant que la source que vous lisez est un objet ou a une conversion implicite en objet. Et, un littéral numérique a en effet une conversion implicite en un objet (un objet Number).

C'est l'une de ces fonctionnalités souvent inconnues de Javascript. Les types Number, Booleanet Stringen Javascript sont généralement stockées en interne comme primitives ( et non des objets à part entière). Il s'agit d'une représentation de stockage compacte et immuable (probablement réalisée de cette façon pour l'efficacité de la mise en œuvre). Mais, Javascript veut que vous puissiez traiter ces primitives comme des objets avec des propriétés et des méthodes. Ainsi, si vous essayez d'accéder à une propriété ou une méthode qui n'est pas directement prise en charge sur la primitive, Javascript contraindra temporairement la primitive dans un type d'objet approprié avec la valeur définie sur la valeur de la primitive.

Lorsque vous utilisez une syntaxe de type objet sur une primitive telle que 0[0], l'interpréteur le reconnaît comme un accès à une propriété sur une primitive. Sa réponse à cela est de prendre la première 0primitive numérique et de la forcer à devenir un Numberobjet à part entière sur lequel elle peut ensuite accéder à la [0]propriété. Dans ce cas spécifique, la [0]propriété d'un objet Number undefinedest la raison pour laquelle c'est la valeur que vous obtenez 0[0].

Voici un article sur l'auto-conversion d'une primitive en objet à des fins de gestion des propriétés:

La vie secrète des primitives Javascript


Voici les parties pertinentes de la spécification ECMAScript 5.1:

9.10 CheckObjectCoercible

Lance TypeError si la valeur est undefinedou null, sinon renvoie true.

entrez la description de l'image ici

11.2.1 Accesseurs de propriété

  1. Soit baseReference le résultat de l'évaluation de MemberExpression.
  2. Soit baseValue GetValue (baseReference).
  3. Soit propertyNameReference le résultat de l'évaluation d'Expression.
  4. Soit propertyNameValue GetValue (propertyNameReference).
  5. Appelez CheckObjectCoercible (baseValue).
  6. Soit propertyNameString ToString (propertyNameValue).
  7. Si la production syntaxique en cours d'évaluation est contenue dans du code en mode strict, laissez strict être vrai, sinon laissez strict être faux.
  8. Renvoie une valeur de type Reference dont la valeur de base est baseValue et dont le nom référencé est propertyNameString et dont l'indicateur de mode strict est strict.

Une partie opérationnelle de cette question est l'étape n ° 5 ci-dessus.

8.7.1 GetValue (V)

Cela décrit comment, lorsque la valeur en cours d'accès est une référence de propriété, elle appelle ToObject(base)pour obtenir la version objet de n'importe quelle primitive.

9.9 ToObject

Il décrit comment Boolean, Numberet Stringprimitives sont converties en une forme d'objet avec le [[PrimitiveValue]] de jeu de propriétés internes en conséquence.


Comme test intéressant, si le code était comme ça:

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

Cela ne lèverait toujours pas une SyntaxError au moment de l'analyse car il s'agit d'une syntaxe techniquement légale, mais cela lèverait une TypeError au moment de l'exécution lorsque vous exécutez le code car lorsque la logique des accesseurs de propriété ci-dessus est appliquée à la valeur de x, il appellera CheckObjectCoercible(x)ou appellera ToObject(x)qui lancera tous les deux une TypeError si xest nullou undefined.


0[1,2]est également valable, qu'est-ce que cela signifie? (Je mets à jour la question)
Michael M.

Et cela ne soulève pas d'erreur de syntaxe, car accéder aux propriétés sur tout ce qui n'est pas nullou undefinedest totalement correct, même si ces propriétés n'existent pas.
user4642212

6
@Michael pas besoin de mettre à jour. C'est un opérateur virgule donc c'est juste0[2]
Amit Joki

1
Opérateur virgule: évalue à la fois 1 et 2 1,2mais renvoie 2.
user4642212

2
C'est une réponse tellement géniale pour la nuance d'une question.
tbh__

20

Comme la plupart des langages de programmation, JS utilise une grammaire pour analyser votre code et le convertir en une forme exécutable. S'il n'y a pas de règle dans la grammaire qui puisse être appliquée à un morceau de code particulier, cela lève une SyntaxError. Sinon, le code est considéré comme valide, peu importe s'il a du sens ou non.

Les parties pertinentes de la grammaire JS sont

Literal :: 
   NumericLiteral
   ...

PrimaryExpression :
   Literal
   ...

MemberExpression :
   PrimaryExpression
   MemberExpression [ Expression ]
   ...

Comme il 0[0]est conforme à ces règles, il est considéré comme une expression valide . Que ce soit correct (par exemple, ne lance pas d'erreur au moment de l'exécution) est une autre histoire, mais oui. Voici comment JS évalue des expressions telles que someLiteral[someExpression]:

  1. évaluer someExpression(qui peut être arbitraire complexe)
  2. convertir le littéral en un type d'objet correspondant (littéraux numériques => Number, chaînes => Stringetc.)
  3. appeler l' get propertyopération sur result (2) avec le nom de propriété result (1)
  4. rejeter le résultat (2)

Donc 0[0]est interprété comme

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

Voici un exemple d' expression valide mais incorrecte :

null[0]

Il est bien analysé, mais au moment de l'exécution, l'interpréteur échoue à l'étape 2 (car nullil ne peut pas être converti en objet) et renvoie une erreur d'exécution.


1
Il y a plus que ça. var x = null; var a = x[0];ne génère pas d'erreur de syntaxe, mais renvoie une TypeError à l'exécution.
jfriend00

@ jfriend00: ce n'est pas le sujet de la question, mais ajouté.
georg

le résultat ne doit pas être indéfini. Il est possible de 0[0]renvoyer une valeur au lieu d'indéfini
Rune FS

9

Il existe des situations où vous pouvez valablement inscrire un nombre en Javascript:

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

Bien que la raison pour laquelle vous souhaiteriez faire cela ne soit pas immédiatement évidente, l'indexation en Javascript équivaut à l'utilisation de la notation pointée (bien que la notation pointée vous limite à l'utilisation d'identificateurs comme clés).


@AmitJoki C'est la même chose que (0).toString(sans appeler la fonction). C'est une propriété du type numérique.
user4642212

@AmitJoki parce qu'il répond à la question «pourquoi cette ligne est-elle valide».
Duncan

@Duncan mais c'est plutôt "ce qui est la notation entre crochets" et je suppose qu'OP le sait. Le fait qu'il soit interprété comme un objet Number, puis que sa 0propriété soit accédée et puisqu'il n'existe pas, undefinedest plus correct comme expliqué dans jfriend00.
Amit Joki

@AmitJoki c'est une hypothèse incorrecte qui 0[0]retournera undefined. Il est probable que ce soit le cas, mais ce n'est pas forcément le cas
Rune FS

9

Je voudrais juste noter que cette syntaxe valide n'est en aucun cas unique à Javascript. La plupart des langages auront une erreur d'exécution ou une erreur de type, mais ce n'est pas la même chose qu'une erreur de syntaxe. Javascript choisit de renvoyer undefined dans de nombreuses situations où un autre langage peut déclencher une exception, y compris lors de l'indexation d'un objet qui n'a pas de propriété du nom donné.

La syntaxe ne connaît pas le type d'une expression (même une expression simple comme un littéral numérique) et vous permettra d'appliquer n'importe quel opérateur à n'importe quelle expression. Par exemple, une tentative d'indice undefinedou nullprovoque un TypeErroren Javascript. Ce n'est pas une erreur de syntaxe - si cela n'est jamais exécuté (être du mauvais côté d'une instruction if), cela ne posera aucun problème, alors qu'une erreur de syntaxe est par définition toujours interceptée au moment de la compilation (eval, Function, etc. , tous comptent comme compilation).


8

Parce que c'est une syntaxe valide, et même du code valide à interpréter. Vous pouvez essayer d'accéder à n'importe quelle propriété de n'importe quel objet (et dans ce cas 0 sera converti en objet Number), et il vous donnera la valeur si elle existe, sinon non définie. Tenter d'accéder à une propriété non définie ne fonctionne pas cependant, donc 0 [0] [0] entraînerait une erreur d'exécution. Cela serait toujours classé comme syntaxe valide. Il y a une différence entre ce qui est une syntaxe valide et ce qui ne causera pas d'erreurs d'exécution / de compilation.


3

Non seulement la syntaxe est valide, mais le résultat n'a pas à l'être undefineddans la plupart, sinon dans tous les cas sains, il le sera. JS est l'un des langages orientés objet les plus purs. La plupart des langages OO sont orientés classe, dans le sens où vous ne pouvez pas changer la forme (elle est liée à la classe) de l'objet une fois créé, seulement l'état de l'objet. Dans JS, vous pouvez changer l'état ainsi que la forme de l'objet et vous le faites plus souvent que vous ne le pensez. Cette capacité donne un code plutôt obscur, si vous en faites un mauvais usage. Les chiffres sont immuables, vous ne pouvez donc pas changer l'objet lui-même, ni son état ni sa forme, vous pouvez donc le faire

0[0] = 1;

qui est une expression d'affectation valide qui renvoie 1 mais n'affecte en fait rien, Le chiffre 0est immuable. Ce qui en soi est un peu étrange. Vous pouvez avoir une expression d'assingment (exécutable) valide et correcte, qui n'attribue rien (*). Cependant, le type du chiffre est un objet mutable, vous pouvez donc muter le type, et les modifications se répercuteront le long de la chaîne du prototype.

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]); 

bien sûr, c'est loin de la catégorie d'utilisation saine, mais le langage est spécifié pour permettre cela car dans d'autres scénarios, l'extension des capacités des objets a en fait beaucoup de sens. C'est ainsi que les plugins jQuery se connectent à l'objet jQuery pour donner un exemple.

(*) Il attribue en fait la valeur 1 à la propriété d'un objet, mais il n'y a aucun moyen de référencer cet objet (transcient) et il sera donc collecté lors de la passe GC nexx


3

En JavaScript, tout est objet, donc lorsque l'interpréteur l'analyse, il traite 0 comme un objet et essaie de renvoyer 0 comme propriété. La même chose se produit lorsque vous essayez d'accéder au 0e élément de true ou "" (chaîne vide).

Même si vous définissez 0 [0] = 1, il définira la propriété et sa valeur en mémoire, mais pendant que vous accédez à 0, il le traite comme un nombre (ne vous trompez pas entre le traitement d'objet et de nombre ici.)

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.