Por que ++ [[]] [+ []] + [+ []] retorna a sequência "10"?


1658

Isso é válido e retorna a string "10"em JavaScript ( mais exemplos aqui ):

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

Por quê? O que esta acontecendo aqui?


446
Comece entendendo que +[]lança uma matriz vazia para 0... depois desperdice uma tarde ...;)
deceze


10
Dê uma olhada no wtfjs.com - ele tem algumas coisas assim com explicações.
ThiefMaster

3
@ deceze, onde você aprende esse tipo de coisa? Quais livros? Estou aprendendo JS do MDN e eles não ensinam essas coisas
Siddharth Thevaril

6
@SiddharthThevaril Da mesma maneira que você acabou de publicar: alguém postou sobre isso em algum lugar e eu li.
deceze

Respostas:


2072

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 Apara 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:

  1. Expr seja o resultado da avaliação da UnaryExpression.

  2. Retornar ToNumber (GetValue (expr)).

ToNumber() diz:

Objeto

Aplique as seguintes etapas:

  1. Deixe primValue ser ToPrimitive (argumento de entrada, dica String).

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

  1. Permita que toString seja o resultado da chamada do método interno [[Get]] do objeto O com o argumento "toString".

  2. 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 .toStringde uma matriz diz:

15.4.4.2 Array.prototype.toString ()

Quando o método toString é chamado, as seguintes etapas são executadas:

  1. Deixe a matriz ser o resultado da chamada de ToObject neste valor.

  2. Seja func como resultado da chamada do método interno [[Get]] da matriz com o argumento "join".

  3. Se IsCallable (func) for false, permita que func seja o método interno padrão Object.prototype.toString (15.2.4.2).

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

  1. Expr seja o resultado da avaliação da UnaryExpression.

  2. Retornar ToNumber (GetValue (expr)).

ToNumberé definido ""como:

O MV do StringNumericLiteral ::: [vazio] é 0.

Então +"" === 0e assim +[] === 0.


8
@ harper: É o estrito verificador de igualdade, ou seja, ele só retorna truese 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).
Pimvdb

41
Parte disso não está correto. A expressão se resume a 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
Tim Down

6
@ Tim Down: Você está completamente correto. Estou tentando corrigir isso, mas ao tentar fazer isso, encontrei outra coisa. Não tenho certeza de como isso é possível. ++[[]][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?
Pimvdb 9/09/11

11
@ pimvdb: Tenho certeza de que o problema está na PutValuechamada (na terminologia ES3, 8.7.2) na operação de prefixo. PutValuerequer 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, ++atrabalha) 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.
Tim Down

13
@pimvdb: Então, após a execução 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.
Tim Down

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

Então nós temos uma concatenação de strings

1+[0].toString() = 10

7
Não seria mais claro escrever ===do que =>?
Mateen Ulhaq

61

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 0pelo ToNumber . Agora podemos substituir 0cada 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 0e esse valor é incrementado por 1para nos dar um valor final de 1. O valor da propriedade 0na matriz externa é atualizado 1e 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 ...


24

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"

13

Este avalia para o mesmo, mas um pouco menor

+!![]+''+(+[])
  • [] - é uma matriz convertida que é convertida em 0 quando você adiciona ou subtrai dela; portanto, + [] = 0
  • ! [] - avalia como falso, portanto, portanto! [] avalia como verdadeiro
  • + !! [] - converte o valor verdadeiro em um valor numérico que é avaliado como verdadeiro, portanto, neste caso 1
  • + '' - acrescenta uma string vazia à expressão, fazendo com que o número seja convertido em string
  • + [] - avalia como 0

então é avaliado como

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

Então agora você conseguiu, tente este:

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

Bem, não, ainda avalia para "10". No entanto, isso é feito de uma maneira diferente. Tente avaliar isso em um inspetor de javascript, como o chrome ou algo assim.
Vlad Shlosberg

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

Este avalia para o mesmo, mas é ainda menor que o seu:"10"
ADJenks 23/01

7

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


4

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. !0converte em true. +trueconverte 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 Objectprotó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 Objectsresulta em duas adições NaN:

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

1
  1. Unário mais a sequência especificada são convertidos em número
  2. O operador de incremento dado a string converte e incrementa em 1
  3. [] == ''. String vazia
  4. + '' ou + [] avalia 0.

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

1
A resposta é confusa / confusa, IOW errada. não[] é equivalente a . Primeiro, o elemento é extraído e depois convertido por . ""++
PointedEars 12/01

1

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 0dentro 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"...

Por que retorna a string "10"?

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.