Esto es válido y devuelve la cadena "10"
en JavaScript ( más ejemplos aquí ):
console.log(++[[]][+[]]+[+[]])
¿Por qué? ¿Que está sucediendo aquí?
Esto es válido y devuelve la cadena "10"
en JavaScript ( más ejemplos aquí ):
console.log(++[[]][+[]]+[+[]])
¿Por qué? ¿Que está sucediendo aquí?
Respuestas:
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 A
para 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, 1
se 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:
Deje que expr sea el resultado de evaluar UnaryExpression.
Regresar a número (GetValue (expr)).
ToNumber()
dice:
Objeto
Aplica los siguientes pasos:
Deje que primValue sea ToPrimitive (argumento de entrada, cadena de sugerencia).
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:
Deje que toString sea el resultado de llamar al método interno [[Get]] del objeto O con el argumento "toString".
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 .toString
de una matriz dice:
15.4.4.2 Array.prototype.toString ()
Cuando se llama al método toString, se toman los siguientes pasos:
Deje que la matriz sea el resultado de llamar a ToObject en este valor.
Deje que func sea el resultado de llamar al método interno de matriz [[Get]] con el argumento "join".
Si IsCallable (func) es falso, entonces deje que func sea el método incorporado estándar Object.prototype.toString (15.2.4.2).
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:
Deje que expr sea el resultado de evaluar UnaryExpression.
Regresar a número (GetValue (expr)).
ToNumber
se define ""
como:
El MV de StringNumericLiteral ::: [vacío] es 0.
Así +"" === 0
y así +[] === 0
.
true
si 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).
1 + [0]
, no "1" + [0]
, porque el ++
operador prefijo ( ) siempre devuelve un número. Ver bclary.com/2004/11/07/#a-11.4.4
++[[]][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?
PutValue
llamada (en la terminología de ES3, 8.7.2) en la operación de prefijo. PutValue
requiere 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 ++a
funciona) 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.
var a = []; ++a
, a
es 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.
++[[]][+[]] => 1 // [+[]] = [0], ++0 = 1
[+[]] => [0]
Entonces tenemos una concatenación de cuerdas
1+[0].toString() = 10
===
que =>
?
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 0
por ToNumber . Ahora podemos sustituir 0
cada 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 0
y este valor se incrementa 1
para darnos un valor final de 1
. El valor de la propiedad 0
en la matriz externa se actualiza 1
y 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 ...
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"
Este se evalúa igual pero un poco más pequeño
+!![]+''+(+[])
entonces se evalúa a
+(true) + '' + (0)
1 + '' + 0
"10"
Así que ahora lo tienes, prueba este:
_=$=+[],++_+''+$
"10"
+ [] 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.
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. !0
convierte a true
. +true
se 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 Object
prototipo 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 Objects
resultados adicionales suman NaN
:
[] + [] // ""
[1] + [2] // "12"
{} + {} // NaN
{a:1} + {b:2} // NaN
[1, {}] + [2, {}] // "1,[object Object]2,[object Object]"
+ '' o + [] evalúa 0.
++[[]][+[]]+[+[]] = 10
++[''][0] + [0] : First part is gives zeroth element of the array which is empty string
1+0
10
[]
es no equivalente a ""
. Primero se extrae el elemento, luego se convierte por ++
.
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 0
dentro 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"
...
+[]
arroja una matriz vacía para0
... luego desperdiciar una tarde ...;)