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

1772

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

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

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

7
  • 490
    Comience entendiendo que +[]arroja una matriz vacía para 0... luego desperdiciar una tarde ...;) 26 de agosto de 2011 a las 8:51
  • 15
    Stackoverflow.com/questions/4170978/explain-why-this-works relacionados . 26 de agosto de 2011 a las 8:52
  • 14
    Eche un vistazo a wtfjs.com , tiene bastantes cosas así con explicaciones. 26 de agosto de 2011 a las 8:58
  • 4
    @deceze, ¿dónde aprendes ese tipo de cosas? ¿Qué libros? Estoy aprendiendo JS de MDN y no enseñan estas cosas 23/11/2016 a las 15:16
  • 8
    @SiddharthThevaril De la misma manera que lo acabas de hacer: alguien publicó sobre eso en alguna parte y yo lo leí. 23/11/2016 a las 20:21
2172

Si lo dividimos, el lío es igual a:

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

En JavaScript, es cierto que +[] === 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. Reemplacemos []por A:

(+[] + 1)
+
[0]

Antes de que +[]pueda coaccionar la matriz en el número 0, primero debe coaccionarse en una cadena, que es "", nuevamente. Finalmente, 1se agrega, lo que da como resultado 1.

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

Simpliquémoslo aún más:

1
+
[0]

Además, esto es cierto en JavaScript:, [0] == "0"porque está uniendo una matriz con un elemento. La unión 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 al mismo tipo. Primero, la matriz se convierte en una cadena "0", luego, el número se convierte en una cadena ( "1"). Cadena de número Cadena+=== .

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

Detalles de la especificación para +[]:

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

11.4.6 Unary + Operator

The unary + operator converts its operand to Number type.

The production UnaryExpression : + UnaryExpression is evaluated as follows:

  1. Let expr be the result of evaluating UnaryExpression.

  2. Return ToNumber(GetValue(expr)).

ToNumber() dice:

Object

Apply the following steps:

  1. Let primValue be ToPrimitive(input argument, hint String).

  2. Return ToString(primValue).

ToPrimitive() dice:

Object

Return a default value for the Object. The default value of an object is retrieved by calling the [[DefaultValue]] internal method of the object, passing the optional hint PreferredType. The behaviour of the [[DefaultValue]] internal method is defined by this specification for all native ECMAScript objects in 8.12.8.

[[DefaultValue]] dice:

8.12.8 [[DefaultValue]] (hint)

When the [[DefaultValue]] internal method of O is called with hint String, the following steps are taken:

  1. Let toString be the result of calling the [[Get]] internal method of object O with argument "toString".

  2. If IsCallable(toString) is true then,

a. Let str be the result of calling the [[Call]] internal method of toString, with O as the this value and an empty argument list.

b. If str is a primitive value, return str.

El .toStringde una matriz dice:

15.4.4.2 Array.prototype.toString ( )

When the toString method is called, the following steps are taken:

  1. Let array be the result of calling ToObject on the this value.

  2. Let func be the result of calling the [[Get]] internal method of array with argument "join".

  3. If IsCallable(func) is false, then let func be the standard built-in method Object.prototype.toString (15.2.4.2).

  4. Return the result of calling the [[Call]] internal method of func providing array as the this value and an empty arguments list.

Así que +[]se reduce a +""porque [].join() === "".

Nuevamente, +se define como:

11.4.6 Unary + Operator

The unary + operator converts its operand to Number type.

The production UnaryExpression : + UnaryExpression is evaluated as follows:

  1. Let expr be the result of evaluating UnaryExpression.

  2. Return ToNumber(GetValue(expr)).

ToNumberse define ""como:

The MV of StringNumericLiteral ::: [empty] is 0.

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

14
  • 9
    @harper: Es el verificador de igualdad estricto, es decir, solo devuelve truesi el valor y el tipo son iguales. 0 == ""devuelve true(lo mismo después de la conversión de tipo), pero 0 === ""es false(no los mismos tipos).
    pimvdb
    26/08/11 a las 14:04
  • 47
    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 9 de septiembre de 2011 a las 14:10
  • 7
    @Tim Down: Tienes toda la razón. Estoy tratando de corregir esto, pero al intentar hacerlo encontré algo más. No estoy seguro de cómo es posible. ++[[]][0]devuelve de hecho 1, pero ++[]arroja un error. Esto es notable porque parece ++[[]][0]que se reduce a ++[]. ¿Quizás tiene alguna idea de por qué ++[]arroja un error mientras ++[[]][0]que no?
    pimvdb
    9/09/11 a las 14:42
  • 13
    @pimvdb: Estoy bastante seguro de que el problema está en la PutValuellamada (en terminología ES3, 8.7.2) en la operación de prefijo. PutValuerequiere una referencia, mientras que []como expresión por sí sola no produce una referencia. Una expresión que contiene una referencia de variable (digamos que la definimos 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, también necesita un lugar para poner ese valor. 9/09/11 a las 15:36
  • 14
    @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. 9 de septiembre de 2011 a las 15:50
129
++[[]][+[]] => 1 // [+[]] = [0], ++0 = 1
[+[]] => [0]

Entonces tenemos una concatenación de cadenas

1+[0].toString() = 10
2
  • 8
    ¿No sería más claro escribir en ===lugar de hacerlo =>? 19 de diciembre de 2018 a las 2:46
  • 2
    @MateenUlhaq Tal vez sería más claro, pero no sería completamente correcto, ya que se [+[]] === [0]evalúa como falso en JS.
    tjespe
    7 nov.20 a las 13:06
70

Lo siguiente es una adaptación de una publicación de blog que responde a 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 aparecerá en ningún entorno de producción (sano) y solo tiene alguna utilidad 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, al igual que algunas de las conversiones comunes, pero muchos de los detalles en este caso no lo son.

La expresión ++[[]][+[]]+[+[]]inicialmente puede parecer 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 verificarlo, 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 satisfacer mismo por qué esto es cierto, revisar el operador unario + y seguir el rastro ligeramente tortuoso que termina con ToPrimitive convertir la matriz vacía en una cadena vacía, que luego se convierte finalmente a 0por ToNumber . Ahora podemos sustituir 0cada instancia de +[]:

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

Ya es más sencillo. 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í mismo 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 solo []y lo 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 ++deja esto en 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 poner el nuevo valor (cualquiera que sea) porque no hay ninguna referencia a una propiedad o variable del objeto para actualizar. En términos de especificaciones, esto está cubierto por la operación interna PutValue , a la que llama el operador de incremento de prefijo.

Entonces, ¿qué hace ++[[]][0]? Bueno, con una lógica similar a +[], la matriz interna se convierte en 0y este valor se incrementa en 1para darnos un valor final de 1. El valor de la propiedad 0en la matriz externa se actualiza 1y se evalúa toda la expresión 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 alguno de los valores primitivos es una cadena, se realiza una concatenación de cadenas; de lo contrario, se realiza una suma numérica. [0]convierte a "0", por lo que se usa la concatenación de cadenas, produciendo "10".

Como comentario final, algo que puede no ser evidente de inmediato es que anular uno 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 ...

0
28

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"
15

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

+!![]+''+(+[])
  • [] - es una matriz que se convierte que se convierte a 0 cuando se le suma o se le 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, por lo que en este caso 1
  • + '': agrega una cadena vacía a la expresión, lo que hace que el número se convierta en cadena
  • + [] - evalúa a 0

por lo que se evalúa a

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

Así que ahora lo tienes, prueba este:

_=$=+[],++_+''+$
3
  • Bueno, no, todavía se evalúa como "10". Sin embargo, esto lo está haciendo de una manera diferente. Intente evaluar esto en un inspector de JavaScript como Chrome o algo así. 30 de diciembre de 2011 a las 6:38
  • _ = $ = + [], ++ _ + '' + $ -> _ = $ = 0, ++ _ + '' + $ -> _ = 0, $ = 0, ++ _ + '' + $ -> ++ 0 + '' + 0 -> 1 + '' + 0 -> '10' // Sí: v 28 de abril de 2018 a las 4:33
  • 4
    Este se evalúa igual pero es incluso más pequeño que el tuyo: "10"
    ADJenks
    23/01/20 a las 20:44
8

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

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

5

Paso a paso, +convierta el valor en un número y si agrega a una matriz vacía +[]... ya que está vacía y es igual a 0, lo hará

Entonces, a partir de ahí, ahora mire su código, es ++[[]][+[]]+[+[]]...

Y hay un plus entre ellos ++[[]][+[]]+[+[]]

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

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

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

Así que puedes imaginar, 1+ "0"será "10"...

¿Por qué devuelve la cadena "10"?

5

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

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

Explicación

  • +!+[]:
    • +[]se evalúa como 0.
    • !0se evalúa como true.
    • +truese evalúa como 1.
  • -~[]es el mismo -(-1)que se evalúa como 1.
  • [+[]]:
    • +[] se evalúa como 0
    • [0]es una matriz con un solo elemento 0.

A continuación, se evalúa la JS 1 + [0], un número + matriz de expresión. Entonces la especificación ECMA funciona: el +operador convierte ambos operandos en una cadena llamando a las operaciones abstractas ToPrimitive y ToString . Opera como una función aditiva si ambos operandos de una expresión son solo números. El truco es que las matrices coaccionan 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)"
[] + []           // ""
[1] + [2]         // "12"
{} + {}           // "[object Object][object Object]" ¹
{a:1} + {b:2}     // "[object Object][object Object]" ¹
[1, {}] + [2, {}] // "1,[object Object]2,[object Object]"

¹: Tenga en cuenta que cada línea se evalúa en un contexto de expresión. El primero {... }es un objeto literal , no un bloque, como sería el caso en un contexto de declaración. En un REPL, puede ver el {} + {}resultado NaN, porque la mayoría de los REPL operan en un contexto de declaración; aquí, el primero {}es un bloque , y el código es equivalente a {}; +{};, con la declaración de expresión final (cuyo valor se convierte en el resultado del registro de finalización) NaNporque el unario +coacciona el objeto a un número.

2
  1. Unario más la cadena dada se convierte en número
  2. La cadena dada por el operador de incremento se convierte e incrementa 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
  • 2
    La respuesta es confusa / confusa, IOW mal. no[] es equivalente a . Primero se extrae el elemento, luego se convierte por .""++ 12 de enero de 2012 a las 4:41
0
++[[]][+[]]+[+[]]
             ^^^
             |
             v
++[[]][+[]]+[0]
       ^^^
       |
       v
++[[]][0]+[0]
  ^^^^^^^
  |
  v
++[]+[0]
     ^^^
     |
     v
++[]+"0"
^^^^
|
v
++0+"0"
^^^
|
v
1+"0"
^^^^^
|
v
"10"

El +operador coacciona cualquier operando no numérico mediante .valueOf(). Si eso no devuelve un número, .toString()se invoca.

Podemos verificar esto simplemente con:

const x = [], y = [];
x.valueOf = () => (console.log('x.valueOf() has been called'), y.valueOf());
x.toString = () => (console.log('x.toString() has been called'), y.toString());
console.log(`+x -> ${+x}`);

Entonces +[]es lo mismo que forzar ""a un número que es 0.

Si algún operando es una cadena, se +concatena.