¿Por qué ++ [[]] [+ []] + [+ []] devuelve la cadena “10”?


1658

Esto es válido y devuelve la cadena "10"en JavaScript ( más ejemplos aquí ):

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

¿Por qué? ¿Que está sucediendo aquí?


446
Comience por comprender que +[]arroja una matriz vacía para 0... luego desperdiciar una tarde ...;)
deceze


10
Eche un vistazo a wtfjs.com : tiene bastantes cosas como esa con los explicativos.
ThiefMaster

3
@deceze, ¿dónde aprendes ese tipo de cosas? Que libros Estoy aprendiendo JS de MDN y no enseñan estas cosas
Siddharth Thevaril

66
@SiddharthThevaril De la misma manera que lo hiciste: alguien publicó sobre eso en algún lugar y leí por casualidad.
deceze

Respuestas:


2072

Si lo dividimos, el desorden es igual a:

++[[]][+[]]
+
[+[]]

En JavaScript, es cierto eso +[] === 0. +convierte algo en un número, y en este caso se reducirá a +""o 0(consulte los detalles de la especificación a continuación).

Por lo tanto, podemos simplificarlo ( ++tiene precedencia sobre +):

++[[]][0]
+
[0]

Porque [[]][0]significa: obtener el primer elemento de [[]], es cierto que:

[[]][0]devuelve la matriz interna ( []). Debido a las referencias, es incorrecto decirlo [[]][0] === [], pero llamemos a la matriz interna Apara evitar la notación incorrecta.

++antes de su operando significa "incrementar en uno y devolver el resultado incrementado". Entonces ++[[]][0]es equivalente a Number(A) + 1(o +A + 1).

Nuevamente, podemos simplificar el desorden en algo más legible. Vamos a sustituir de []nuevo por A:

(+[] + 1)
+
[0]

Antes de +[]poder forzar la matriz en el número 0, primero debe ser forzada a una cadena, que es "", nuevamente. Finalmente, 1se agrega, lo que resulta en 1.

  • (+[] + 1) === (+"" + 1)
  • (+"" + 1) === (0 + 1)
  • (0 + 1) === 1

Simplifiquemos aún más:

1
+
[0]

Además, esto es cierto en JavaScript: [0] == "0"porque une una matriz con un elemento. Unirse concatenará los elementos separados por ,. Con un elemento, puede deducir que esta lógica dará como resultado el primer elemento en sí.

En este caso, +ve dos operandos: un número y una matriz. Ahora está tratando de obligar a los dos a ser del mismo tipo. Primero, la matriz se convierte en la cadena "0", luego, el número se convierte en una cadena ( "1"). Número +Cadena ===Cadena .

"1" + "0" === "10" // Yay!

Detalles de especificación para +[]:

Esto es todo un laberinto, pero para hacerlo +[], primero se está convirtiendo en una cadena porque eso es lo que +dice:

11.4.6 Operador unario +

El operador unario + convierte su operando a tipo de número.

La producción UnaryExpression: + UnaryExpression se evalúa de la siguiente manera:

  1. Deje que expr sea el resultado de evaluar UnaryExpression.

  2. Regresar a número (GetValue (expr)).

ToNumber() dice:

Objeto

Aplica los siguientes pasos:

  1. Deje que primValue sea ToPrimitive (argumento de entrada, cadena de sugerencia).

  2. Regresar a la cadena (primValue).

ToPrimitive() dice:

Objeto

Devuelve un valor predeterminado para el objeto. El valor predeterminado de un objeto se recupera llamando al método interno [[DefaultValue]] del objeto, pasando la sugerencia opcional PreferredType. El comportamiento del método interno [[DefaultValue]] está definido por esta especificación para todos los objetos ECMAScript nativos en 8.12.8.

[[DefaultValue]] dice:

8.12.8 [[Valor predeterminado]] (sugerencia)

Cuando se llama al método interno [[DefaultValue]] de O con la sugerencia String, se siguen los siguientes pasos:

  1. Deje que toString sea el resultado de llamar al método interno [[Get]] del objeto O con el argumento "toString".

  2. Si IsCallable (toString) es verdadero, entonces,

a. Deje que str sea el resultado de llamar al método interno [[Call]] de toString, con O como este valor y una lista de argumentos vacía.

si. Si str es un valor primitivo, devuelve str.

El .toStringde una matriz dice:

15.4.4.2 Array.prototype.toString ()

Cuando se llama al método toString, se toman los siguientes pasos:

  1. Deje que la matriz sea el resultado de llamar a ToObject en este valor.

  2. Deje que func sea el resultado de llamar al método interno de matriz [[Get]] con el argumento "join".

  3. Si IsCallable (func) es falso, entonces deje que func sea el método incorporado estándar Object.prototype.toString (15.2.4.2).

  4. Devuelve el resultado de llamar al método interno [[Call]] de func proporcionando matriz como este valor y una lista de argumentos vacía.

Entonces +[]se reduce a +"", porque [].join() === "".

Nuevamente, el +se define como:

11.4.6 Operador unario +

El operador unario + convierte su operando a tipo de número.

La producción UnaryExpression: + UnaryExpression se evalúa de la siguiente manera:

  1. Deje que expr sea el resultado de evaluar UnaryExpression.

  2. Regresar a número (GetValue (expr)).

ToNumberse define ""como:

El MV de StringNumericLiteral ::: [vacío] es 0.

Así +"" === 0y así +[] === 0.


8
@harper: es el verificador estricto de igualdad, es decir, solo devuelve truesi tanto el valor como el tipo son iguales. 0 == ""devuelve true(lo mismo después de la conversión de tipos), pero 0 === ""es false(no los mismos tipos).
pimvdb

41
Parte de esto no es correcto. La expresión se reduce a 1 + [0], no "1" + [0], porque el ++operador prefijo ( ) siempre devuelve un número. Ver bclary.com/2004/11/07/#a-11.4.4
Tim Down

66
@Tim Down: Estás completamente en lo correcto. Estoy tratando de corregir esto, pero cuando intenté hacerlo encontré algo más. No estoy seguro de cómo es esto posible. ++[[]][0]vuelve de hecho 1, pero ++[]arroja un error. Esto es notable porque parece ++[[]][0]que se reduce a ++[]. ¿Quizás tienes alguna idea de por qué ++[]arroja un error mientras ++[[]][0]que no?
pimvdb

11
@pimvdb: estoy bastante seguro de que el problema está en la PutValuellamada (en la terminología de ES3, 8.7.2) en la operación de prefijo. PutValuerequiere una referencia mientras que []como una expresión por sí sola no produce una referencia. Una expresión que contiene una referencia variable (digamos que habíamos definido previamente y var a = []luego ++afunciona) o el acceso a la propiedad de un objeto (como [[]][0]) produce una Referencia. En términos más simples, el operador de prefijo no solo produce un valor, sino que también necesita un lugar para poner ese valor.
Tim Down

13
@pimvdb: Entonces, después de ejecutar var a = []; ++a, aes 1. Después de ejecutar ++[[]][0], la matriz creada por la [[]]expresión ahora contiene solo el número 1 en el índice 0. ++requiere una Referencia para hacer esto.
Tim Down

124
++[[]][+[]] => 1 // [+[]] = [0], ++0 = 1
[+[]] => [0]

Entonces tenemos una concatenación de cuerdas

1+[0].toString() = 10

77
¿No sería más claro escribir ===que =>?
Mateen Ulhaq

61

Lo siguiente está adaptado de una publicación de blog respondiendo esta pregunta que publiqué mientras esta pregunta aún estaba cerrada. Los enlaces son a (una copia HTML de) la especificación ECMAScript 3, que sigue siendo la línea de base para JavaScript en los navegadores web más utilizados en la actualidad.

Primero, un comentario: este tipo de expresión nunca va a aparecer en ningún entorno de producción (sano) y solo es útil como ejercicio de cuán bien el lector conoce los bordes sucios de JavaScript. El principio general de que los operadores de JavaScript convierten implícitamente entre tipos es útil, como lo son algunas de las conversiones comunes, pero gran parte de los detalles en este caso no lo son.

La expresión ++[[]][+[]]+[+[]]puede parecer inicialmente bastante imponente y oscura, pero en realidad es relativamente fácil dividirla en expresiones separadas. A continuación, simplemente agregué paréntesis para mayor claridad; Puedo asegurarle que no cambian nada, pero si desea verificar eso, no dude en leer sobre el operador de agrupación . Entonces, la expresión se puede escribir más claramente como

( ++[[]][+[]] ) + ( [+[]] )

Desglosando esto, podemos simplificar observando que +[]evalúa a 0. Para satisfacerse por qué esto es cierto, consulte el operador unario + y siga el rastro ligeramente tortuoso que termina con ToPrimitive convirtiendo la matriz vacía en una cadena vacía, que finalmente es convertida 0por ToNumber . Ahora podemos sustituir 0cada instancia de +[]:

( ++[[]][0] ) + [0]

Más simple ya. En cuanto a ++[[]][0], esa es una combinación del operador de incremento de prefijo ( ++), un literal de matriz que define una matriz con un solo elemento que es en sí misma una matriz vacía ( [[]]) y un descriptor de acceso de propiedad ( [0]) llamado en la matriz definida por el literal de matriz.

Entonces, podemos simplificar [[]][0]a justos []y tenemos ++[], ¿verdad? De hecho, este no es el caso porque la evaluación ++[]arroja un error, que inicialmente puede parecer confuso. Sin embargo, un poco de reflexión sobre la naturaleza de ++esto lo deja claro: se usa para incrementar una variable (por ejemplo ++i) o una propiedad de objeto (por ejemplo ++obj.count). No solo evalúa un valor, sino que también almacena ese valor en algún lugar. En el caso de ++[], no tiene dónde colocar el nuevo valor (sea lo que sea) porque no hay referencia a una propiedad o variable de objeto para actualizar. En términos específicos , esto está cubierto por la operación interna PutValue , que es llamada por el operador de incremento de prefijo.

Entonces, ¿qué hace ++[[]][0]? Bueno, por una lógica similar +[], la matriz interna se convierte 0y este valor se incrementa 1para darnos un valor final de 1. El valor de la propiedad 0en la matriz externa se actualiza 1y toda la expresión se evalúa como 1.

Esto nos deja con

1 + [0]

... que es un uso simple del operador de suma . Ambos operandos se convierten primero en primitivas y si cualquiera de los valores primitivos es una cadena, se realiza la concatenación de cadenas, de lo contrario se realiza la suma numérica. [0]convierte a "0", por lo que se utiliza la concatenación de cadenas, produciendo "10".

Como último comentario, algo que puede no ser inmediatamente aparente es que anular cualquiera de los métodos toString()o cambiará el resultado de la expresión, porque ambos se verifican y se usan si están presentes al convertir un objeto en un valor primitivo. Por ejemplo, lo siguientevalueOf()Array.prototype

Array.prototype.toString = function() {
  return "foo";
};
++[[]][+[]]+[+[]]

... produce "NaNfoo". Por qué sucede esto se deja como ejercicio para el lector ...


24

Hagámoslo simple:

++[[]][+[]]+[+[]] = "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"

13

Este se evalúa igual pero un poco más pequeño

+!![]+''+(+[])
  • [] - es una matriz convertida que se convierte a 0 cuando la agrega o resta, por lo tanto, + [] = 0
  • ! [] - se evalúa como falso, por lo tanto !! [] se evalúa como verdadero
  • + !! [] - convierte el verdadero en un valor numérico que se evalúa como verdadero, así que en este caso 1
  • + '': agrega una cadena vacía a la expresión haciendo que el número se convierta en cadena
  • + [] - evalúa a 0

entonces se evalúa a

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

Así que ahora lo tienes, prueba este:

_=$=+[],++_+''+$

Pues no, todavía se evalúa a "10". Sin embargo, esto lo está haciendo de una manera diferente. Intente evaluar esto en un inspector de JavaScript como Chrome o algo así.
Vlad Shlosberg

_ = $ = + [], ++ _ + '' + $ -> _ = $ = 0, ++ _ + '' + $ -> _ = 0, $ = 0, ++ _ + '' + $ -> ++ 0 + '' + 0 -> 1 + '' + 0 -> '10' // Yei: v
LeagueOfJava

Este se evalúa igual pero es aún más pequeño que el tuyo:"10"
ADJenks

7

+ [] se evalúa a 0 [...] y luego lo suma (+ operación) con cualquier cosa convierte el contenido de la matriz a su representación de cadena que consiste en elementos unidos con una coma.

Cualquier otra cosa como tomar el índice de la matriz (tiene mayor prioridad que la operación +) es ordinal y no es nada interesante.


4

Quizás las formas más cortas posibles de evaluar una expresión en "10" sin dígitos son:

+!+[] + [+[]] // "10"

-~[] + [+[]] // "10"

// ========== Explicación ========== \\

+!+[]: +[]Convierte a 0. !0convierte a true. +truese convierte en 1. -~[]= -(-1)que es 1

[+[]]: +[]Convierte a 0. [0]es una matriz con un solo elemento 0.

Entonces JS evalúa la expresión 1 + [0], por lo tanto Number + Array. Entonces, la especificación ECMA funciona: el +operador convierte ambos operandos en una cadena llamando a las toString()/valueOf()funciones desde el Objectprototipo base . Funciona como una función aditiva si ambos operandos de una expresión son solo números. El truco es que las matrices convierten fácilmente sus elementos en una representación de cadena concatenada.

Algunos ejemplos:

1 + {} //    "1[object Object]"
1 + [] //    "1"
1 + new Date() //    "1Wed Jun 19 2013 12:13:25 GMT+0400 (Caucasus Standard Time)"

Hay una buena excepción de que dos Objectsresultados adicionales suman NaN:

[] + []   //    ""
[1] + [2] //    "12"
{} + {}   //    NaN
{a:1} + {b:2}     //    NaN
[1, {}] + [2, {}] //    "1,[object Object]2,[object Object]"

1
  1. La cadena unaria más dada se convierte en número
  2. Incremente las conversiones e incrementos de cadena dados por el operador en 1
  3. [] == ''. Cuerda vacía
  4. + '' o + [] evalúa 0.

    ++[[]][+[]]+[+[]] = 10 
    ++[''][0] + [0] : First part is gives zeroth element of the array which is empty string 
    1+0 
    10

1
La respuesta es confusa / confusa, IOW mal. []es no equivalente a "". Primero se extrae el elemento, luego se convierte por ++.
PointedEars

1

Paso a paso, +cambie el valor a un número y si agrega a una matriz vacía +[]... como está vacía y es igual a 0, será

A partir de ahí, ahora mira tu código, es ++[[]][+[]]+[+[]]...

Y hay más entre ellos ++[[]][+[]]+[+[]]

Entonces estos [+[]]regresarán [0]ya que tienen una matriz vacía que se convierte 0dentro de la otra matriz ...

Entonces, como se imagina, el primer valor es una matriz bidimensional con una matriz dentro ... por [[]][+[]]lo que será igual a la [[]][0]que volverá []...

Y al final ++convertirlo y aumentarlo a 1...

Para que puedas imaginar, 1+ "0"será "10"...

¿Por qué devuelve la cadena "10"?

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.