Isso é válido e retorna a string "10"
em JavaScript ( mais exemplos aqui ):
console.log(++[[]][+[]]+[+[]])
Por quê? O que esta acontecendo aqui?
Isso é válido e retorna a string "10"
em JavaScript ( mais exemplos aqui ):
console.log(++[[]][+[]]+[+[]])
Por quê? O que esta acontecendo aqui?
Respostas:
Se dividirmos, a bagunça é igual a:
++[[]][+[]]
+
[+[]]
Em JavaScript, é verdade isso +[] === 0
. +
converte algo em um número e, nesse caso, será reduzido a +""
ou 0
(consulte os detalhes das especificações abaixo).
Portanto, podemos simplificá-lo ( ++
tem precedência sobre +
):
++[[]][0]
+
[0]
Porque [[]][0]
significa: obter o primeiro elemento [[]]
, é verdade que:
[[]][0]
retorna a matriz interna ( []
). Devido às referências, é errado dizer [[]][0] === []
, mas vamos chamar a matriz interna A
para evitar a notação errada.
++
antes de seu operando significa "incrementar por um e retornar o resultado incrementado". Então ++[[]][0]
é equivalente a Number(A) + 1
(ou +A + 1
).
Novamente, podemos simplificar a bagunça em algo mais legível. Vamos substituir []
novamente por A
:
(+[] + 1)
+
[0]
Antes de +[]
poder coagir a matriz no número 0
, ela precisa ser coagida em uma cadeia primeiro, ou seja ""
, novamente. Por fim, 1
é adicionado, o que resulta em 1
.
(+[] + 1) === (+"" + 1)
(+"" + 1) === (0 + 1)
(0 + 1) === 1
Vamos simplificar ainda mais:
1
+
[0]
Além disso, isso é verdade no JavaScript: [0] == "0"
porque está juntando uma matriz com um elemento. A junção concatenará os elementos separados por ,
. Com um elemento, você pode deduzir que essa lógica resultará no próprio elemento.
Nesse caso, +
vê dois operandos: um número e uma matriz. Agora, ele está tentando coagir os dois no mesmo tipo. Primeiro, o array é coagido na string "0"
; em seguida, o número é coagido em uma string ( "1"
). Number +
String ===
String .
"1" + "0" === "10" // Yay!
Detalhes da especificação para +[]
:
Este é um labirinto, mas, para fazer +[]
, primeiro ele está sendo convertido em uma string, porque é o que +
diz:
11.4.6 Unário + Operador
O operador unary + converte seu operando no tipo Number.
A produção UnaryExpression: + UnaryExpression é avaliada da seguinte maneira:
Expr seja o resultado da avaliação da UnaryExpression.
Retornar ToNumber (GetValue (expr)).
ToNumber()
diz:
Objeto
Aplique as seguintes etapas:
Deixe primValue ser ToPrimitive (argumento de entrada, dica String).
Retorne ToString (primValue).
ToPrimitive()
diz:
Objeto
Retorne um valor padrão para o objeto. O valor padrão de um objeto é recuperado chamando o método interno [[DefaultValue]] do objeto, passando a dica opcional PreferredType. O comportamento do método interno [[DefaultValue]] é definido por esta especificação para todos os objetos nativos do ECMAScript na versão 8.12.8.
[[DefaultValue]]
diz:
8.12.8 [[DefaultValue]] (dica)
Quando o método interno [[DefaultValue]] de O é chamado com a dica String, são executadas as seguintes etapas:
Permita que toString seja o resultado da chamada do método interno [[Get]] do objeto O com o argumento "toString".
Se IsCallable (toString) for verdadeiro,
uma. Seja str o resultado da chamada do método interno [[Call]] de toString, com O como este valor e uma lista de argumentos vazia.
b. Se str for um valor primitivo, retorne str.
O .toString
de uma matriz diz:
15.4.4.2 Array.prototype.toString ()
Quando o método toString é chamado, as seguintes etapas são executadas:
Deixe a matriz ser o resultado da chamada de ToObject neste valor.
Seja func como resultado da chamada do método interno [[Get]] da matriz com o argumento "join".
Se IsCallable (func) for false, permita que func seja o método interno padrão Object.prototype.toString (15.2.4.2).
Retorne o resultado da chamada do método interno [[Call]] da função de fornecimento de funções como o valor this e uma lista de argumentos vazia.
Então tudo +[]
se resume a +""
, porque [].join() === ""
.
Novamente, o +
é definido como:
11.4.6 Unário + Operador
O operador unary + converte seu operando no tipo Number.
A produção UnaryExpression: + UnaryExpression é avaliada da seguinte maneira:
Expr seja o resultado da avaliação da UnaryExpression.
Retornar ToNumber (GetValue (expr)).
ToNumber
é definido ""
como:
O MV do StringNumericLiteral ::: [vazio] é 0.
Então +"" === 0
e assim +[] === 0
.
true
se o valor e o tipo forem iguais. 0 == ""
retorna true
(o mesmo após a conversão de tipo), mas 0 === ""
é false
(não é o mesmo tipo).
1 + [0]
, não "1" + [0]
, porque o ++
operador prefix ( ) sempre retorna um número. Veja bclary.com/2004/11/07/#a-11.4.4
++[[]][0]
retorna de fato 1
, mas ++[]
gera um erro. Isso é notável porque parece que ++[[]][0]
tudo se resume a ++[]
. Você talvez tenha alguma idéia de por que ++[]
lança um erro ++[[]][0]
e não o faz?
PutValue
chamada (na terminologia ES3, 8.7.2) na operação de prefixo. PutValue
requer uma referência, enquanto []
uma expressão por si só não produz uma referência. Uma expressão que contém uma referência variável (dizer que tinha previamente definido var a = []
, em seguida, ++a
trabalha) ou acesso a propriedade de um objeto (como [[]][0]
) produz uma referência. Em termos mais simples, o operador de prefixo não apenas produz um valor, mas também precisa de um lugar para colocar esse valor.
var a = []; ++a
, a
é 1. Após a execução ++[[]][0]
, a matriz criada pela [[]]
expressão agora contém apenas o número 1 no índice 0. ++
requer uma Referência para fazer isso.
++[[]][+[]] => 1 // [+[]] = [0], ++0 = 1
[+[]] => [0]
Então nós temos uma concatenação de strings
1+[0].toString() = 10
===
do que =>
?
O seguinte é adaptado de uma postagem de blog que respondeu a essa pergunta que eu postei enquanto ela ainda estava fechada. Os links são para (uma cópia em HTML) da especificação do ECMAScript 3, ainda a linha de base do JavaScript nos navegadores da web mais usados atualmente.
Primeiro, um comentário: esse tipo de expressão nunca será exibido em qualquer ambiente de produção (sadio) e é de alguma utilidade como um exercício de quão bem o leitor conhece as arestas sujas do JavaScript. O princípio geral de que os operadores JavaScript convertem implicitamente entre tipos é útil, assim como algumas conversões comuns, mas muitos dos detalhes nesse caso não são.
A expressão ++[[]][+[]]+[+[]]
pode inicialmente parecer bastante imponente e obscura, mas na verdade é relativamente fácil dividir-se em expressões separadas. Abaixo, simplesmente adicionei parênteses para maior clareza; Posso garantir que eles não mudam nada, mas se você quiser verificar isso, fique à vontade para ler sobre o operador de agrupamento . Portanto, a expressão pode ser escrita mais claramente como
( ++[[]][+[]] ) + ( [+[]] )
Por fim, podemos simplificar observando o que é +[]
avaliado como 0
. Para saber por que isso é verdade, confira o operador unary + e siga a trilha um pouco tortuosa que acaba com o ToPrimitive convertendo a matriz vazia em uma string vazia, que é finalmente convertida 0
pelo ToNumber . Agora podemos substituir 0
cada instância de +[]
:
( ++[[]][0] ) + [0]
Já é mais simples. Quanto a ++[[]][0]
isso, é uma combinação do operador de incremento de prefixo ( ++
), um literal de matriz que define uma matriz com um único elemento que é uma matriz vazia ( [[]]
) e um acessador de propriedade ( [0]
) chamado na matriz definida pelo literal da matriz.
Então, podemos simplificar [[]][0]
para justamente []
e temos ++[]
, certo? De fato, esse não é o caso, porque a avaliação ++[]
gera um erro, que pode inicialmente parecer confuso. No entanto, um pouco de reflexão sobre a natureza de ++
deixa isso claro: é usado para incrementar uma variável (por exemplo ++i
) ou uma propriedade de objeto (por exemplo ++obj.count
). Além de avaliar um valor, ele também armazena esse valor em algum lugar. No caso de ++[]
, ele não tem onde colocar o novo valor (seja ele qual for), porque não há referência a uma propriedade ou variável de objeto a ser atualizada. Em termos de especificação, isso é coberto pela operação PutValue interna , chamada pelo operador de incremento de prefixo.
Então, o que ++[[]][0]
faz? Bem, por uma lógica semelhante à +[]
, a matriz interna é convertida em 0
e esse valor é incrementado por 1
para nos dar um valor final de 1
. O valor da propriedade 0
na matriz externa é atualizado 1
e a expressão inteira é avaliada em 1
.
Isso nos deixa com
1 + [0]
... que é um uso simples do operador de adição . Ambos os operandos são primeiro convertidos em primitivos e se um dos valores primitivos for uma sequência, a concatenação da sequência será executada, caso contrário, a adição numérica será realizada. [0]
converte em "0"
, então a concatenação de string é usada, produzindo "10"
.
Como um aspecto final, algo que pode não ser imediatamente aparente é que a substituição de um dos métodos toString()
ou alterará o resultado da expressão, porque ambos são verificados e usados se presentes ao converter um objeto em um valor primitivo. Por exemplo, o seguintevalueOf()
Array.prototype
Array.prototype.toString = function() {
return "foo";
};
++[[]][+[]]+[+[]]
... produz "NaNfoo"
. Por que isso acontece é deixado como um exercício para o leitor ...
Vamos simplificar:
++[[]][+[]]+[+[]] = "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 avalia para o mesmo, mas um pouco menor
+!![]+''+(+[])
então é avaliado como
+(true) + '' + (0)
1 + '' + 0
"10"
Então agora você conseguiu, tente este:
_=$=+[],++_+''+$
"10"
+ [] é avaliado como 0 e, em seguida, somado (+ operação) com qualquer coisa converte o conteúdo da matriz em sua representação de string, consistindo em elementos unidos por vírgula.
Qualquer outra coisa como pegar o índice da matriz (tem prioridade maior que + operação) é ordinal e não é nada interessante.
Talvez as formas mais curtas possíveis de avaliar uma expressão em "10" sem dígitos sejam:
+!+[] + [+[]]
// "10"
-~[] + [+[]]
// "10"
// ========== Explicação ========== \\
+!+[]
: +[]
Converte em 0. !0
converte em true
. +true
converte para 1.
-~[]
= -(-1)
que é 1
[+[]]
: +[]
Converte em 0. [0]
é uma matriz com um único elemento 0.
Então JS avalia a expressão 1 + [0]
, portanto Number + Array
. Em seguida, a especificação ECMA funciona: o +
operador converte os dois operandos em uma string chamando as toString()/valueOf()
funções do Object
protótipo de base . Funciona como uma função aditiva se os dois operandos de uma expressão forem apenas números. O truque é que as matrizes convertem facilmente seus elementos em uma representação de string concatenada.
Alguns exemplos:
1 + {} // "1[object Object]"
1 + [] // "1"
1 + new Date() // "1Wed Jun 19 2013 12:13:25 GMT+0400 (Caucasus Standard Time)"
Há uma boa exceção que Objects
resulta em duas adições NaN
:
[] + [] // ""
[1] + [2] // "12"
{} + {} // NaN
{a:1} + {b:2} // NaN
[1, {}] + [2, {}] // "1,[object Object]2,[object Object]"
+ '' ou + [] avalia 0.
++[[]][+[]]+[+[]] = 10
++[''][0] + [0] : First part is gives zeroth element of the array which is empty string
1+0
10
[]
é equivalente a . Primeiro, o elemento é extraído e depois convertido por . ""
++
Passo a passo disso, +
vire valor para um número e se você adicionar a uma matriz vazia +[]
... como ela está vazia e é igual a 0
, ela será
Então, a partir daí, agora observe seu código, é ++[[]][+[]]+[+[]]
...
E há mais entre eles ++[[]][+[]]
+[+[]]
Então, eles [+[]]
retornarão [0]
como eles têm uma matriz vazia que é convertida para 0
dentro da outra matriz ...
Então, como imaginamos, o primeiro valor é uma matriz bidimensional com uma matriz dentro ... então [[]][+[]]
será igual ao [[]][0]
que retornará []
...
E no final ++
converta e aumente para 1
...
Então você pode imaginar, 1
+ "0"
será "10"
...
+[]
lança uma matriz vazia para0
... depois desperdice uma tarde ...;)