Ceci est valide et renvoie la chaîne "10"
en JavaScript ( plus d'exemples ici ):
console.log(++[[]][+[]]+[+[]])
Pourquoi? Que se passe-t-il ici?
Ceci est valide et renvoie la chaîne "10"
en JavaScript ( plus d'exemples ici ):
console.log(++[[]][+[]]+[+[]])
Pourquoi? Que se passe-t-il ici?
Réponses:
Si nous le séparons, le désordre est égal à:
++[[]][+[]]
+
[+[]]
En JavaScript, c'est vrai que +[] === 0
. +
convertit quelque chose en nombre, et dans ce cas, il descendra à +""
ou 0
(voir les détails de la spécification ci-dessous).
Par conséquent, nous pouvons le simplifier ( ++
a priorité sur +
):
++[[]][0]
+
[0]
Parce que [[]][0]
signifie: obtenir le premier élément de [[]]
, il est vrai que:
[[]][0]
renvoie le tableau intérieur ( []
). En raison de références, il est faux de le dire [[]][0] === []
, mais appelons le tableau interne A
pour éviter la mauvaise notation.
++
avant son opérande signifie «incrémenter de un et retourner le résultat incrémenté». ++[[]][0]
Est donc équivalent à Number(A) + 1
(ou +A + 1
).
Encore une fois, nous pouvons simplifier le désordre en quelque chose de plus lisible. Remplaçons de []
retour A
:
(+[] + 1)
+
[0]
Avant de +[]
pouvoir contraindre le tableau dans le nombre 0
, il doit d'abord être contraint dans une chaîne, ce qui est ""
encore une fois. Enfin, 1
est ajouté, ce qui se traduit par 1
.
(+[] + 1) === (+"" + 1)
(+"" + 1) === (0 + 1)
(0 + 1) === 1
Simplifions encore plus:
1
+
[0]
En outre, cela est vrai en JavaScript:, [0] == "0"
car il joint un tableau avec un élément. La jointure concaténera les éléments séparés par ,
. Avec un élément, vous pouvez déduire que cette logique se traduira par le premier élément lui-même.
Dans ce cas, +
voit deux opérandes: un nombre et un tableau. Il essaie maintenant de contraindre les deux dans le même type. Tout d'abord, le tableau est contraint dans la chaîne "0"
, ensuite, le nombre est contraint dans une chaîne ( "1"
). Chaîne de nombre Chaîne+
===
.
"1" + "0" === "10" // Yay!
Détails des spécifications pour +[]
:
C'est tout un labyrinthe, mais pour ce faire +[]
, il est d'abord converti en chaîne car c'est ce qui +
dit:
11.4.6 Opérateur unaire +
L'opérateur unaire + convertit son opérande en type Number.
La production UnaryExpression: + UnaryExpression est évaluée comme suit:
Soit expr le résultat de l'évaluation de UnaryExpression.
Return ToNumber (GetValue (expr)).
ToNumber()
dit:
Objet
Appliquez les étapes suivantes:
Soit primValue ToPrimitive (argument d'entrée, indice String).
Retournez àString (primValue).
ToPrimitive()
dit:
Objet
Renvoie une valeur par défaut pour l'objet. La valeur par défaut d'un objet est récupérée en appelant la méthode interne [[DefaultValue]] de l'objet, en passant l'indicateur facultatif PreferredType. Le comportement de la méthode interne [[DefaultValue]] est défini par cette spécification pour tous les objets ECMAScript natifs en 8.12.8.
[[DefaultValue]]
dit:
8.12.8 [[DefaultValue]] (indice)
Lorsque la méthode interne [[DefaultValue]] de O est appelée avec hint String, les étapes suivantes sont effectuées:
Soit toString le résultat de l'appel de la méthode interne [[Get]] de l'objet O avec l'argument "toString".
Si IsCallable (toString) est vrai, alors,
une. Soit str le résultat de l'appel de la méthode interne [[Call]] de toString, avec O comme valeur this et une liste d'arguments vide.
b. Si str est une valeur primitive, retournez str.
Le .toString
tableau indique:
15.4.4.2 Array.prototype.toString ()
Lorsque la méthode toString est appelée, les étapes suivantes sont effectuées:
Soit array le résultat de l'appel de ToObject sur la valeur this.
Soit func le résultat de l'appel de la méthode interne de tableau [[Get]] avec l'argument "join".
Si IsCallable (func) est faux, alors func est la méthode intégrée standard Object.prototype.toString (15.2.4.2).
Renvoie le résultat de l'appel de la méthode interne [[Call]] de func fournissant le tableau comme valeur this et une liste d'arguments vide.
Cela +[]
revient donc à +""
, parce que [].join() === ""
.
Encore une fois, le +
est défini comme:
11.4.6 Opérateur unaire +
L'opérateur unaire + convertit son opérande en type Number.
La production UnaryExpression: + UnaryExpression est évaluée comme suit:
Soit expr le résultat de l'évaluation de UnaryExpression.
Return ToNumber (GetValue (expr)).
ToNumber
est défini ""
comme:
Le MV de StringNumericLiteral ::: [vide] est 0.
Donc +"" === 0
, et donc +[] === 0
.
true
si la valeur et le type sont identiques. 0 == ""
renvoie true
(même après la conversion de type), mais 0 === ""
est false
(pas les mêmes types).
1 + [0]
, non "1" + [0]
, car l' ++
opérateur prefix ( ) renvoie toujours un nombre. Voir bclary.com/2004/11/07/#a-11.4.4
++[[]][0]
renvoie en effet 1
, mais ++[]
renvoie une erreur. C'est remarquable car il semble que ++[[]][0]
cela se résume à ++[]
. Avez-vous peut-être une idée de pourquoi ++[]
jette une erreur alors ++[[]][0]
que non?
PutValue
appel (dans la terminologie ES3, 8.7.2) dans l'opération de préfixe. PutValue
nécessite une référence alors []
qu'une expression à elle seule ne produit pas de référence. Une expression contenant une référence variable (disons que nous avions précédemment définie var a = []
puis ++a
fonctionne) ou l'accès à la propriété d'un objet (tel que [[]][0]
) produit une référence. En termes plus simples, l'opérateur de préfixe produit non seulement une valeur, il a également besoin d'un endroit pour mettre cette valeur.
var a = []; ++a
, a
est égal à 1. Après l'exécution ++[[]][0]
, le tableau créé par l' [[]]
expression contient désormais uniquement le numéro 1 à l'index 0. ++
nécessite une référence pour ce faire.
++[[]][+[]] => 1 // [+[]] = [0], ++0 = 1
[+[]] => [0]
Ensuite, nous avons une concaténation de chaînes
1+[0].toString() = 10
===
plutôt que =>
?
Ce qui suit est adapté d'un article de blog répondant à cette question que j'ai posté alors que cette question était encore fermée. Les liens sont vers (une copie HTML de) la spécification ECMAScript 3, toujours la référence pour JavaScript dans les navigateurs Web couramment utilisés aujourd'hui.
Tout d'abord, un commentaire: ce type d'expression ne se présentera jamais dans un environnement de production (sain) et n'est utile que comme exercice pour savoir à quel point le lecteur connaît les bords sales de JavaScript. Le principe général selon lequel les opérateurs JavaScript convertissent implicitement entre les types est utile, comme le sont certaines des conversions courantes, mais la plupart des détails dans ce cas ne le sont pas.
L'expression ++[[]][+[]]+[+[]]
peut initialement sembler plutôt imposante et obscure, mais est en fait relativement facile à décomposer en expressions distinctes. Ci-dessous, j'ai simplement ajouté des parenthèses pour plus de clarté; Je peux vous assurer qu'ils ne changent rien, mais si vous voulez le vérifier, n'hésitez pas à vous renseigner sur l' opérateur de regroupement . Ainsi, l'expression peut être écrite plus clairement comme
( ++[[]][+[]] ) + ( [+[]] )
En décomposant cela, nous pouvons simplifier en observant qui +[]
évalue à 0
. Pour vous convaincre de la raison pour laquelle cela est vrai, consultez l' opérateur unaire + et suivez la piste légèrement tortueuse qui se termine par ToPrimitive convertissant le tableau vide en une chaîne vide, qui est ensuite finalement convertie en 0
par ToNumber . Nous pouvons maintenant remplacer 0
chaque instance de +[]
:
( ++[[]][0] ) + [0]
Déjà plus simple. Quant à ++[[]][0]
, c'est une combinaison de l' opérateur d'incrémentation de préfixe ( ++
), un littéral de tableau définissant un tableau avec un élément unique qui est lui-même un tableau vide ( [[]]
) et un accesseur de propriété ( [0]
) appelé sur le tableau défini par le littéral de tableau.
Donc, nous pouvons simplifier [[]][0]
juste []
et nous avons ++[]
, non? En fait, ce n'est pas le cas car l'évaluation ++[]
génère une erreur, ce qui peut sembler initialement déroutant. Cependant, un peu de réflexion sur la nature de ++
cela le montre clairement: il est utilisé pour incrémenter une variable (par exemple ++i
) ou une propriété d'objet (par exemple ++obj.count
). Non seulement il évalue une valeur, mais il stocke également cette valeur quelque part. Dans le cas de ++[]
, il n'a nulle part où mettre la nouvelle valeur (quelle qu'elle soit) car il n'y a aucune référence à une propriété ou variable d'objet à mettre à jour. En termes de spécification, cela est couvert par l' opération PutValue interne , qui est appelée par l'opérateur d'incrémentation de préfixe.
Alors, qu'est-ce que ça ++[[]][0]
fait? Eh bien, selon une logique similaire à +[]
, le tableau interne est converti en 0
et cette valeur est incrémentée de 1
pour nous donner une valeur finale de 1
. La valeur de la propriété 0
dans le tableau externe est mise à jour 1
et l'expression entière est évaluée 1
.
Cela nous laisse avec
1 + [0]
... qui est une utilisation simple de l' opérateur d'addition . Les deux opérandes sont d'abord convertis en primitives et si l'une ou l'autre valeur primitive est une chaîne, la concaténation de chaîne est effectuée, sinon l'addition numérique est effectuée. [0]
convertit en "0"
, donc la concaténation de chaînes est utilisée, produisant "10"
.
Enfin, quelque chose qui peut ne pas être immédiatement apparent est que le remplacement de l'une des méthodes toString()
ou changera le résultat de l'expression, car les deux sont vérifiés et utilisés s'ils sont présents lors de la conversion d'un objet en valeur primitive. Par exemple, les éléments suivantsvalueOf()
Array.prototype
Array.prototype.toString = function() {
return "foo";
};
++[[]][+[]]+[+[]]
... produit "NaNfoo"
. Pourquoi cela se produit est laissé comme un exercice pour le lecteur ...
Rendons les choses simples:
++[[]][+[]]+[+[]] = "10"
var a = [[]][+[]];
var b = [+[]];
// so a == [] and b == [0]
++a;
// then a == 1 and b is still that array [0]
// when you sum the var a and an array, it will sum b as a string just like that:
1 + "0" = "10"
Celui-ci est identique mais un peu plus petit
+!![]+''+(+[])
il en va de même pour
+(true) + '' + (0)
1 + '' + 0
"10"
Alors maintenant vous avez cela, essayez celui-ci:
_=$=+[],++_+''+$
"10"
+ [] évalue à 0 [...] puis l'addition (+ opération) avec n'importe quoi convertit le contenu du tableau en sa représentation sous forme de chaîne composée d'éléments joints par une virgule.
Tout autre chose comme prendre l'index du tableau (avoir une priorité de râpe supérieure à + opération) est ordinale et n'a rien d'intéressant.
Les moyens les plus courts possibles d'évaluer une expression en "10" sans chiffres sont peut-être les suivants:
+!+[] + [+[]]
// "dix"
-~[] + [+[]]
// "dix"
// ========== Explication ========== \\
+!+[]
: +[]
Convertit en 0. !0
convertit en true
. +true
convertit en 1.
-~[]
= -(-1)
qui est 1
[+[]]
: +[]
Convertit en 0. [0]
est un tableau avec un seul élément 0.
Ensuite, JS évalue l' expression 1 + [0]
, ainsi Number + Array
. Ensuite, la spécification ECMA fonctionne: l' +
opérateur convertit les deux opérandes en une chaîne en appelant les toString()/valueOf()
fonctions à partir du Object
prototype de base . Il fonctionne comme une fonction additive si les deux opérandes d'une expression sont uniquement des nombres. L'astuce est que les tableaux convertissent facilement leurs éléments en une représentation sous forme de chaîne concaténée.
Quelques exemples:
1 + {} // "1[object Object]"
1 + [] // "1"
1 + new Date() // "1Wed Jun 19 2013 12:13:25 GMT+0400 (Caucasus Standard Time)"
Il y a une belle exception: deux Objects
ajouts entraînent NaN
:
[] + [] // ""
[1] + [2] // "12"
{} + {} // NaN
{a:1} + {b:2} // NaN
[1, {}] + [2, {}] // "1,[object Object]2,[object Object]"
+ '' ou + [] évalue 0.
++[[]][+[]]+[+[]] = 10
++[''][0] + [0] : First part is gives zeroth element of the array which is empty string
1+0
10
[]
n'est pas équivalent à ""
. L'élément est d'abord extrait, puis converti par ++
.
Pas à pas de cela, +
transformez la valeur en nombre et si vous ajoutez à un tableau vide +[]
... comme il est vide et est égal à 0
, il
Alors à partir de là, regardez maintenant votre code, c'est ++[[]][+[]]+[+[]]
...
Et il y a plus entre eux ++[[]][+[]]
+[+[]]
Donc, ceux- [+[]]
ci reviendront [0]
car ils ont un tableau vide qui est converti à l' 0
intérieur de l'autre tableau ...
Donc, comme imaginez, la première valeur est un tableau à 2 dimensions avec un tableau à l'intérieur ... [[]][+[]]
sera donc égal à celui [[]][0]
qui retournera []
...
Et à la fin, ++
convertissez-le et augmentez-le pour 1
...
Vous pouvez donc imaginer, 1
+ "0"
sera "10"
...
+[]
jette un tableau vide dans0
... puis perdez un après-midi ...;)