Perché ++ ] ] + ] restituisce la stringa “10”?

Questo è valido e restituisce la stringa "10" in JavaScript ( altri esempi qui ):

Perché? Cosa sta succedendo qui?

Se lo dividiamo, il casino è uguale a:

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

In JavaScript, è vero che +[] === 0 . + converte qualcosa in un numero, e in questo caso arriverà a +"" o 0 (vedi i dettagli delle specifiche sotto).

Pertanto, possiamo semplificarlo ( ++ ha la precedenza su + ):

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

Perché [[]][0] significa: ottieni il primo elemento da [[]] , è vero che:

  • [[]][0] restituisce l’array interno ( [] ). A causa di riferimenti è sbagliato dire [[]][0] === [] , ma chiamiamo l’array interno A per evitare la notazione sbagliata.
  • ++[[]][0] == A + 1 , poiché ++ significa ‘incrementa di uno’.
  • ++[[]][0] === +(A + 1) ; in altre parole, sarà sempre un numero ( +1 non restituisce necessariamente un numero, mentre ++ fa sempre – grazie a Tim Down per averlo indicato).

Ancora una volta, possiamo semplificare il disordine in qualcosa di più leggibile. Sostituiamo [] indietro per A :

 +([] + 1) + [0] 

Anche in JavaScript, questo è vero: [] + 1 === "1" , perché [] == "" (unendosi a un array vuoto), quindi:

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

Semplifichiamolo ancora di più:

 1 + [0] 

Inoltre, questo è vero in JavaScript: [0] == "0" , perché sta unendo un array con un elemento. L’unione concatenerà gli elementi separati da,. Con un elemento, è ansible dedurre che questa logica genererà il primo elemento stesso.

Quindi, alla fine otteniamo (numero + stringa = stringa):

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

Dettagli delle specifiche per +[] :

Questo è piuttosto un labirinto, ma per fare +[] , prima viene convertito in una stringa perché è ciò che dice + :

11.4.6 Unario + Operatore

L’operatore unario + converte il suo operando in tipo Numero.

La produzione UnaryExpression: + UnaryExpression viene valutata come segue:

  1. Lascia che espr sia il risultato della valutazione di UnaryExpression.

  2. Return ToNumber (GetValue (expr)).

ToNumber() dice:

Oggetto

Applica i seguenti passi:

  1. Sia primValue sia ToPrimitive (argomento input, hint String).

  2. Return ToString (primValue).

ToPrimitive() dice:

Oggetto

Restituisce un valore predefinito per l’object. Il valore predefinito di un object viene recuperato chiamando il metodo interno [[DefaultValue]] dell’object, passando il suggerimento opzionale PreferredType. Il comportamento del metodo interno [[DefaultValue]] interno è definito da questa specifica per tutti gli oggetti nativi ECMAScript in 8.12.8.

[[DefaultValue]] dice:

8.12.8 [[DefaultValue]] (suggerimento)

Quando viene chiamato il metodo interno [[DefaultValue]] di O con hint String, vengono eseguiti i seguenti passi:

  1. Lascia che toString sia il risultato della chiamata al metodo interno [[Ottieni]] dell’object O con argomento “toString”.

  2. Se IsCallable (toString) è vero allora,

un. Lascia che str sia il risultato di chiamare il metodo interno [[Call]] di toString, con O come questo valore e un elenco di argomenti vuoto.

b. Se str è un valore primitivo, restituisci str.

Il .toString di un array dice:

15.4.4.2 Array.prototype.toString ()

Quando viene chiamato il metodo toString, vengono eseguiti i seguenti passaggi:

  1. Lasciare che la matrice sia il risultato di chiamare ToObject su questo valore.

  2. Sia func il risultato della chiamata al metodo interno [[Get]] dell’array con l’argomento “join”.

  3. Se IsCallable (func) è false, allora func deve essere il metodo predefinito standard Object.prototype.toString (15.2.4.2).

  4. Restituisce il risultato di chiamare il metodo interno [[Call]] di func fornendo array come questo valore e un elenco di argomenti vuoto.

Quindi +[] scende a +"" , perché [].join() === "" .

Di nuovo, il + è definito come:

11.4.6 Unario + Operatore

L’operatore unario + converte il suo operando in tipo Numero.

La produzione UnaryExpression: + UnaryExpression viene valutata come segue:

  1. Lascia che espr sia il risultato della valutazione di UnaryExpression.

  2. Return ToNumber (GetValue (expr)).

ToNumber è definito per "" come:

MV di StringNumericLiteral ::: [vuoto] è 0.

Quindi +"" === 0 , e quindi +[] === 0 .

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

Quindi abbiamo una concatenazione di stringhe

 1+[0].toString() = 10 

Quanto segue è adattato da un post sul blog che risponde a questa domanda che ho postato mentre questa domanda era ancora chiusa. I collegamenti sono (una copia HTML di) la specifica ECMAScript 3, ancora la linea di base per JavaScript nei browser Web comunemente usati oggi.

Innanzitutto, un commento: questo tipo di espressione non si presenterà mai in nessun ambiente di produzione (sano) ed è solo di qualche utilità come esercizio in quanto il lettore conosce bene i bordi sporchi di JavaScript. Il principio generale che gli operatori JavaScript convertono implicitamente tra tipi è utile, così come alcune delle conversioni comuni, ma gran parte dei dettagli in questo caso non lo è.

L’espressione ++[[]][+[]]+[+[]] potrebbe inizialmente sembrare piuttosto imponente e oscura, ma in realtà è relativamente facile da suddividere in espressioni separate. Sotto ho semplicemente aggiunto parentesi per chiarezza; Posso assicurarti che non cambiano nulla, ma se vuoi verificarlo, non esitare a leggere l’ operatore di raggruppamento . Quindi, l’espressione può essere scritta più chiaramente come

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

Rompendo questo, possiamo semplificare osservando che +[] restituisce 0 . Per accertarti del perché questo è vero, controlla l’ operatore unario + e segui la traccia leggermente tortuosa che finisce con ToPrimitive convertendo l’array vuoto in una stringa vuota, che viene poi convertita a 0 da ToNumber . Ora possiamo sostituire 0 per ogni istanza di +[] :

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

Più semplice già Per quanto riguarda ++[[]][0] , questa è una combinazione dell’operatore di incremento del prefisso ( ++ ), un array letterale che definisce una matrice con un singolo elemento che è esso stesso una matrice vuota ( [[]] ) e una proprietà accessor ( [0] ) ha richiamato l’array definito dal letterale dell’array.

Quindi, possiamo semplificare [[]][0] solo [] e abbiamo ++[] , giusto? In realtà, questo non è il caso perché la valutazione di ++[] genera un errore, che potrebbe inizialmente sembrare confuso. Tuttavia, una piccola riflessione sulla natura di ++ rende chiaro: è usato per incrementare una variabile (ad es. ++i ) o una proprietà di object (ad esempio ++obj.count ). Non solo valuta un valore, ma lo memorizza anche da qualche parte. Nel caso di ++[] , non ha nessun posto dove inserire il nuovo valore (qualunque esso sia) perché non vi è alcun riferimento a una proprietà o variabile di un object da aggiornare. In termini spec, questo è coperto dall’operazione PutValue interna, che viene chiamata dall’operatore di incremento del prefisso.

Allora, cosa fa ++[[]][0] ? Bene, con logica simile a +[] , l’array interno viene convertito in 0 e questo valore viene incrementato di 1 per darci un valore finale di 1 . Il valore della proprietà 0 nell’array esterno viene aggiornato a 1 e l’espressione intera viene valutata su 1 .

Questo ci lascia con

 1 + [0] 

… che è un semplice uso dell’operatore addizione . Entrambi gli operandi vengono prima convertiti in primitive e se il valore primitivo è una stringa, viene eseguita la concatenazione di stringhe, altrimenti viene eseguita l’aggiunta numerica. [0] converte in "0" , quindi viene utilizzata la concatenazione delle stringhe, producendo "10" .

In ultima analisi, qualcosa che potrebbe non essere immediatamente evidente è che l’override di uno dei metodi toString() o valueOf() di Array.prototype cambierà il risultato dell’espressione, poiché entrambi sono controllati e usati se presenti durante la conversione di un object in un valore primitivo. Ad esempio, il seguente

 Array.prototype.toString = function() { return "foo"; }; ++[[]][+[]]+[+[]] 

… produce "NaNfoo" . Perché questo accada è lasciato come esercizio per il lettore …

Facciamo in modo semplice:

 ++[[]][+[]]+[+[]] = "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" 

Questo valuta lo stesso ma un po ‘più piccolo

 +!![]+''+(+[]) 
  • [] – viene convertito un array che viene convertito in 0 quando si aggiunge o si sottrae da esso, quindi quindi + [] = 0
  • ! [] – viene valutato come falso, quindi quindi !! [] restituisce true
  • + !! [] – converte il true in un valore numerico che diventa true, quindi in questo caso 1
  • + ” – aggiunge una stringa vuota all’espressione causando la conversione del numero in stringa
  • + [] – restituisce 0

così è valutato a

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

Quindi ora hai capito, prova questo:

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

+ [] restituisce 0 […] quindi sum (+ operazione) con qualsiasi cosa converte il contenuto dell’array nella sua rappresentazione stringa costituita da elementi uniti con una virgola.

Qualsiasi altra cosa come prendere l’indice dell’array (avere priorità maggiore di + operazione) è ordinale e non è niente di interessante.

Forse il modo più breve per valutare un’espressione in “10” senza cifre è:

+!+[] + [+[]] // “10”

-~[] + [+[]] // “10”

// ========== Spiegazione ========== \\

+!+[] : +[] Converti in 0. !0 converte in true . +true converte in 1. -~[] = -(-1) che è 1

[+[]] : +[] Converti in 0. [0] è un array con un singolo elemento 0.

Quindi JS valuta 1 + [0] , quindi l’espressione Number + Array . Quindi la specifica ECMA funziona: + operatore converte entrambi gli operandi in una stringa chiamando le funzioni toString()/valueOf() dal prototipo Object base. Funziona come una funzione additiva se entrambi gli operandi di un’espressione sono solo numeri. Il trucco è che gli array convertono facilmente i loro elementi in una rappresentazione di stringa concatenata.

Qualche esempio:

 1 + {} // "1[object Object]" 1 + [] // "1" 1 + new Date() // "1Wed Jun 19 2013 12:13:25 GMT+0400 (Caucasus Standard Time)" 

C’è una bella eccezione che l’aggiunta di due Objects risulta in NaN :

 [] + [] // "" [1] + [2] // "12" {} + {} // NaN {a:1} + {b:2} // NaN [1, {}] + [2, {}] // "1,[object Object]2,[object Object]" 
  1. La stringa Unary plus data converte in numero
  2. Incrementa l’operatore dato che la stringa converte e incrementa di 1
  3. [] == ”. Stringa vuota
  4. + ” o + [] valuta 0.

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