Por que ++ ] ] + ] retorna a string “10”?

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

Por quê? O que esta acontecendo aqui?

Se dividirmos, a bagunça é igual a:

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

Em JavaScript, é verdade que +[] === 0 . + converte algo em um número e, nesse caso, ele será reduzido para +"" ou 0 (consulte os detalhes da especificação abaixo).

Portanto, podemos simplificá-lo ( ++ tem precendence over + ):

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

Porque [[]][0] significa: obter o primeiro elemento de [[]] , é verdade que:

  • [[]][0] retorna a matriz interna ( [] ). Devido a referências, é errado dizer [[]][0] === [] , mas vamos chamar o array interno A para evitar a notação errada.
  • ++[[]][0] == A + 1 , uma vez que ++ significa ‘incremento por um’.
  • ++[[]][0] === +(A + 1) ; em outras palavras, sempre será um número ( +1 não necessariamente retorna um número, enquanto ++ sempre faz – graças a Tim Down por apontar isto).

Mais uma vez, podemos simplificar a bagunça em algo mais legível. Vamos replace [] volta por A :

 +([] + 1) + [0] 

Em JavaScript, isso também é verdade: [] + 1 === "1" , porque [] == "" (unindo uma matriz vazia), então:

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

Vamos simplificar ainda mais:

 1 + [0] 

Além disso, isso é verdade em JavaScript: [0] == "0" , porque está unindo uma matriz a um elemento. A junit irá concatenar os elementos separados por. Com um elemento, você pode deduzir que essa lógica resultará no primeiro elemento em si.

Então, no final nós obtemos (number + string = string):

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

Detalhes da especificação para +[] :

Isso é bem um labirinto, mas para fazer +[] , primeiro ele está sendo convertido em uma string porque é o que + diz:

11.4.6 Operador Unary +

O operador unário + converte seu operando em tipo de número.

A produção UnaryExpression: + UnaryExpression é avaliada da seguinte maneira:

  1. Deixe expr ser o resultado da avaliação de UnaryExpression.

  2. Retornar paraNúmero (GetValue (expr)).

ToNumber() diz:

Objeto

Aplique as seguintes etapas:

  1. Deixe primValue ser ToPrimitive (argumento de input, hint String).

  2. Retornar ToString (primValue).

ToPrimitive() diz:

Objeto

Retorna um valor padrão para o object. O valor padrão de um object é recuperado chamando o método interno [[DefaultValue]] do object, passando a dica opcional PreferredType. O comportamento do método interno [[DefaultValue]] é definido por esta especificação para todos os objects ECMAScript nativos em 8.12.8.

[[DefaultValue]] diz:

8.12.8 [[DefaultValue]] (sugestão)

Quando o método interno de [[DefaultValue]] de O é chamado com a string hint, as seguintes etapas são executadas:

  1. Deixe que toString seja o resultado de chamar o método interno [[Get]] do object O com o argumento “toString”.

  2. Se IsCallable (toString) for true,

uma. Deixe str ser o resultado de chamar o método interno de [[Call]] de toString, com O como este valor e uma lista de argumentos vazia.

b. Se str é 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:

  1. Deixe array ser o resultado de chamar ToObject no valor this.

  2. Deixe func ser o resultado de chamar o método interno de array [[Get]] com o argumento “join”.

  3. Se IsCallable (func) for falso, então let func ser o método interno padrão Object.prototype.toString (15.2.4.2).

  4. Retorna o resultado de chamar o método interno [[Chamada]] de func fornecendo matriz como o valor e uma lista de argumentos vazia.

Então +[] se reduz a +"" , porque [].join() === "" .

Novamente, o + é definido como:

11.4.6 Operador Unary +

O operador unário + converte seu operando em tipo de número.

A produção UnaryExpression: + UnaryExpression é avaliada da seguinte maneira:

  1. Deixe expr ser o resultado da avaliação de UnaryExpression.

  2. Retornar paraNúmero (GetValue (expr)).

ToNumber está definido para "" como:

O MV de StringNumericLiteral ::: [empty] é 0.

Então +"" === 0 e, portanto, +[] === 0 .

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

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

 1+[0].toString() = 10 

O seguinte é adaptado de uma postagem no blog respondendo a essa pergunta que eu publiquei enquanto essa questão ainda estava encerrada. Os links são para (uma cópia em HTML) da especificação ECMAScript 3, ainda a linha de base para JavaScript nos navegadores da Web usados ​​atualmente.

Primeiro, um comentário: esse tipo de expressão nunca vai aparecer em nenhum ambiente de produção (sã) e serve apenas como um exercício de quão bem o leitor conhece as bordas sujas do JavaScript. O princípio geral de que operadores JavaScript convertem implicitamente entre tipos é útil, assim como algumas das conversões comuns, mas grande parte dos detalhes, neste caso, não é.

A expressão ++[[]][+[]]+[+[]] pode inicialmente parecer bastante imponente e obscura, mas na verdade é relativamente fácil decompor 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 . Então, a expressão pode ser mais claramente escrita como

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

Quebrando isso, podemos simplificar observando que +[] avaliado como 0 . Para se certificar por que isso é verdade, confira o operador unary + e siga a trilha levemente tortuosa que termina com ToPrimitive convertendo o array vazio em uma string vazia, que é finalmente convertida em 0 por ToNumber . Podemos agora replace 0 por cada instância de +[] :

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

Mais simples já. Quanto a ++[[]][0] , essa é uma combinação do operador de incremento de prefixo ( ++ ), um literal de matriz definindo uma matriz com um único elemento que é um array vazio ( [[]] ) e um acessador de propriedade ( [0] ) chamou a matriz definida pelo literal da matriz.

Então, podemos simplificar [[]][0] para apenas [] e temos ++[] , certo? De fato, este não é o caso porque avaliar ++[] gera um erro, que inicialmente pode parecer confuso. No entanto, um pouco de reflection sobre a natureza do ++ deixa isso claro: ele é usado para incrementar uma variável (por exemplo, ++i ) ou uma propriedade de object (por exemplo, ++obj.count ). Não só avalia um valor, como também armazena esse valor em algum lugar. No caso de ++[] , 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 object a ser atualizada. Em termos de especificação, isso é coberto pela operação PutValue interna, que é chamada pelo operador de incremento de prefixo.

Então, o que faz ++[[]][0] ? Bem, por lógica similar a +[] , a matriz interna é convertida em 0 e este valor é incrementado em 1 para nos dar um valor final de 1 . O valor da propriedade 0 na matriz externa é atualizado para 1 e a expressão inteira é avaliada como 1 .

Isso nos deixa com

 1 + [0] 

… o que é um simples uso do operador de adição . Os dois operandos são primeiro convertidos em primitivos e, se um valor primitivo for uma cadeia, a concatenação de cadeias será executada, caso contrário, a adição numérica será executada. [0] converte para "0" , então a concatenação de strings é usada, produzindo "10" .

Como um aparte final, algo que pode não ser imediatamente aparente é que sobrescrever um dos methods toString() ou valueOf() de Array.prototype irá mudar o resultado da expressão, porque ambos são verificados e usados ​​se estiverem presentes na conversão de uma expressão. object em um valor primitivo. Por exemplo, o seguinte

 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

 +!![]+''+(+[]) 
  • [] – é um array que é convertido para 0 quando você adiciona ou subtrai, portanto + [] = 0
  • ! [] – avalia para falso, portanto, portanto !! [] avalia como verdadeiro
  • + [] – converte o verdadeiro para 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 para 0

então é avaliado para

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

Então agora você tem isso, tente este:

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

+ [] é avaliado como 0 […] e então a sum (+ operação) com qualquer coisa converte o conteúdo da matriz em sua representação de string consistindo em elementos unidos com vírgula.

Qualquer outra coisa, como pegar o índice de array (ter maior prioridade que a operação +) é ordinal e não é nada interessante.

Talvez as maneiras 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 o 1 + [0] , assim a expressão Number + Array . Em seguida, a especificação do ECMA funciona: o operador + converte os dois operandos em uma sequência chamando as funções toString()/valueOf() do protótipo Object base. Ele funciona como uma function aditiva se os dois operandos de uma expressão forem apenas números. O truque é que os arrays 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 dois resultados de adição de Objects em NaN :

 [] + [] // "" [1] + [2] // "12" {} + {} // NaN {a:1} + {b:2} // NaN [1, {}] + [2, {}] // "1,[object Object]2,[object Object]" 
  1. Cadeia unária mais dada converte em número
  2. Incrementar operador dado string converte e incrementa em 1
  3. [] == ”. Corda Vazia
  4. + ” ou + [] avalia 0.

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