Pourquoi ++ [[]] [+ []] + [+ []] retourne la chaîne "10"?

1659

Ceci est valide et renvoie la chaîne "10"en JavaScript ( plus d'exemples ici ):

console.log(++[[]][+[]]+[+[]])

Pourquoi? Que se passe-t-il ici?

SapuSeven
la source
446
Commencez par comprendre que +[]jette un tableau vide dans 0... puis perdez un après-midi ...;)
décompose
10
Jetez un oeil à wtfjs.com - il y a pas mal de choses comme ça avec explatinos.
ThiefMaster
3
@deceze, où apprenez-vous ce genre de choses? Quels livres?
J'apprends
6
@SiddharthThevaril De la même manière que vous venez de le faire: quelqu'un a posté quelque chose à ce sujet et il m'est arrivé de le lire.
décomposer

Réponses:

2072

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 Apour é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, 1est 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:

  1. Soit expr le résultat de l'évaluation de UnaryExpression.

  2. Return ToNumber (GetValue (expr)).

ToNumber() dit:

Objet

Appliquez les étapes suivantes:

  1. Soit primValue ToPrimitive (argument d'entrée, indice String).

  2. 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:

  1. Soit toString le résultat de l'appel de la méthode interne [[Get]] de l'objet O avec l'argument "toString".

  2. 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 .toStringtableau indique:

15.4.4.2 Array.prototype.toString ()

Lorsque la méthode toString est appelée, les étapes suivantes sont effectuées:

  1. Soit array le résultat de l'appel de ToObject sur la valeur this.

  2. Soit func le résultat de l'appel de la méthode interne de tableau [[Get]] avec l'argument "join".

  3. Si IsCallable (func) est faux, alors func est la méthode intégrée standard Object.prototype.toString (15.2.4.2).

  4. 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:

  1. Soit expr le résultat de l'évaluation de UnaryExpression.

  2. Return ToNumber (GetValue (expr)).

ToNumberest défini ""comme:

Le MV de StringNumericLiteral ::: [vide] est 0.

Donc +"" === 0, et donc +[] === 0.

pimvdb
la source
8
@harper: c'est le vérificateur d'égalité stricte, c'est-à-dire qu'il ne retourne que truesi 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).
pimvdb
41
Une partie de cela n'est pas correcte. L'expression se résume à 1 + [0], non "1" + [0], car l' ++opérateur prefix ( ) renvoie toujours un nombre. Voir bclary.com/2004/11/07/#a-11.4.4
Tim Down
6
@Tim Down: vous avez tout à fait raison. J'essaie de corriger cela, mais en essayant de le faire, j'ai trouvé autre chose. Je ne sais pas comment cela est possible. ++[[]][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?
pimvdb
11
@pimvdb: Je suis à peu près sûr que le problème est dans l' PutValueappel (dans la terminologie ES3, 8.7.2) dans l'opération de préfixe. PutValuené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 ++afonctionne) 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.
Tim Down
13
@pimvdb: Donc, après l'exécution var a = []; ++a, aest é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.
Tim Down
124
++[[]][+[]] => 1 // [+[]] = [0], ++0 = 1
[+[]] => [0]

Ensuite, nous avons une concaténation de chaînes

1+[0].toString() = 10
Shef
la source
7
Ne serait-il pas plus clair d'écrire ===plutôt que =>?
Mateen Ulhaq
61

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 0par ToNumber . Nous pouvons maintenant remplacer 0chaque 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 0et cette valeur est incrémentée de 1pour nous donner une valeur finale de 1. La valeur de la propriété 0dans le tableau externe est mise à jour 1et 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 ...

Tim Down
la source
24

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"
renatoluna
la source
13

Celui-ci est identique mais un peu plus petit

+!![]+''+(+[])
  • [] - est un tableau converti qui est converti en 0 lorsque vous en ajoutez ou en soustrayez, donc + [] = 0
  • ! [] - évalue à faux, donc !! [] évalue à vrai
  • + !! [] - convertit le vrai en une valeur numérique qui est évaluée à vrai, donc dans ce cas 1
  • + '' - ajoute une chaîne vide à l'expression provoquant la conversion du nombre en chaîne
  • + [] - correspond à 0

il en va de même pour

+(true) + '' + (0)
1 + '' + 0
"10"

Alors maintenant vous avez cela, essayez celui-ci:

_=$=+[],++_+''+$
Vlad Shlosberg
la source
Eh bien non, il est toujours évalué à "10". Cependant, cela se fait d'une manière différente. Essayez d'évaluer cela dans un inspecteur javascript comme chrome ou quelque chose.
Vlad Shlosberg
_ = $ = + [], ++ _ + '' + $ -> _ = $ = 0, ++ _ + '' + $ -> _ = 0, $ = 0, ++ _ + '' + $ -> ++ 0 + '' + 0 -> 1 + '' + 0 -> '10' // Yei: v
LeagueOfJava
Celui-ci a la même valeur mais il est encore plus petit que le vôtre:"10"
ADJenks
7

+ [] é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.

Eskat0n
la source
4

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. !0convertit en true. +trueconvertit 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 Objectprototype 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 Objectsajouts entraînent NaN:

[] + []   //    ""
[1] + [2] //    "12"
{} + {}   //    NaN
{a:1} + {b:2}     //    NaN
[1, {}] + [2, {}] //    "1,[object Object]2,[object Object]"
AsyncMind
la source
1
  1. Unaire plus une chaîne donnée convertie en nombre
  2. L'opérateur d'incrémentation étant donné que la chaîne convertit et incrémente de 1
  3. [] == ''. Chaîne vide
  4. + '' ou + [] évalue 0.

    ++[[]][+[]]+[+[]] = 10 
    ++[''][0] + [0] : First part is gives zeroth element of the array which is empty string 
    1+0 
    10
Praveen Vedanth
la source
1
La réponse est confuse / déroutante, IOW mal. []n'est pas équivalent à "". L'élément est d'abord extrait, puis converti par ++.
PointedEars
1

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' 0inté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"...

Pourquoi retourne la chaîne "10"?

Alireza
la source