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


1658

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

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

Perché? Cosa sta succedendo qui?


446
Inizia con la comprensione che +[]lancia un array vuoto per 0... quindi spreca un pomeriggio ...;)
ingannare


10
Dai un'occhiata a wtfjs.com - ha un bel po 'di cose del genere con spiegazioni.
ThiefMaster,

3
@deceze, dove impari quel genere di cose? Quali libri? Sto imparando JS da MDN e non insegnano queste cose
Siddharth Thevaril,

6
@SiddharthThevaril Come hai appena fatto: qualcuno l'ha pubblicato da qualche parte e mi è capitato di leggerlo.
Inganno

Risposte:


2072

Se lo dividiamo, il casino è uguale a:

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

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

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

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

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

[[]][0]restituisce l'array interno ( []). A causa dei riferimenti è sbagliato dirlo [[]][0] === [], ma chiamiamo l'array interno Aper evitare la notazione sbagliata.

++prima del suo operando significa "incrementa di uno e restituisce il risultato incrementato". Quindi ++[[]][0]equivale a Number(A) + 1(o +A + 1).

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

(+[] + 1)
+
[0]

Prima di +[]poter forzare l'array nel numero 0, deve prima essere forzato in una stringa, che è "", di nuovo, di nuovo. Infine, 1viene aggiunto, che risulta in 1.

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

Semplificiamo ancora di più:

1
+
[0]

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

In questo caso, +vede due operandi: un numero e un array. Ora sta cercando di forzare i due nello stesso tipo. Innanzitutto, l'array viene forzato nella stringa "0", quindi il numero viene forzato in una stringa ( "1"). 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é è quello 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 expr sia il risultato della valutazione di UnaryExpression.

  2. Return ToNumber (GetValue (expr)).

ToNumber() dice:

Oggetto

Applicare i seguenti passaggi:

  1. Sia primValue essere ToPrimitive (argomento di input, suggerimento String).

  2. Return ToString (primValue).

ToPrimitive() dice:

Oggetto

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

[[DefaultValue]] dice:

8.12.8 [[DefaultValue]] (suggerimento)

Quando il metodo interno [[DefaultValue]] di O viene chiamato con hint String, vengono eseguite le seguenti operazioni:

  1. Sia toString il risultato della chiamata al metodo interno [[Get]] dell'oggetto O con argomento "toString".

  2. Se IsCallable (toString) è true,

un. Sia str il risultato della chiamata al metodo interno [[Call]] di toString, con O come questo valore e un elenco di argomenti vuoto.

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

Il .toStringdi un array dice:

15.4.4.2 Array.prototype.toString ()

Quando viene chiamato il metodo toString, vengono eseguite le seguenti operazioni:

  1. Lascia che array sia il risultato della chiamata a ToObject con questo valore.

  2. Lascia che func sia il risultato della chiamata al metodo interno [[Get]] dell'array con argomento "join".

  3. Se IsCallable (func) è falso, allora lasciare che func sia il metodo incorporato standard Object.prototype.toString (15.2.4.2).

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

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

Ancora una volta, +viene 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 expr sia il risultato della valutazione di UnaryExpression.

  2. Return ToNumber (GetValue (expr)).

ToNumberè definito ""come:

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

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


8
@harper: è il rigoroso controllo dell'uguaglianza, cioè restituisce solo truese sia il valore che il tipo sono uguali. 0 == ""ritorna true(lo stesso dopo la conversione del tipo), ma 0 === ""è false(non gli stessi tipi).
pimvdb,

41
Parte di questo non è corretta. L'espressione si riduce a 1 + [0], non "1" + [0]perché l' ++operatore prefix ( ) restituisce sempre un numero. Vedi bclary.com/2004/11/07/#a-11.4.4
Tim Down

6
@Tim Down: hai completamente ragione. Sto cercando di correggere questo, ma quando ho provato a farlo ho trovato qualcos'altro. Non sono sicuro di come sia possibile. ++[[]][0]ritorna davvero 1, ma ++[]genera un errore. Questo è straordinario perché sembra ++[[]][0]ridursi a ++[]. Hai forse idea del perché ++[]genera un errore, mentre ++[[]][0]non lo fa?
pimvdb,

11
@pimvdb: sono abbastanza sicuro che il problema sia nella PutValuechiamata (nella terminologia ES3, 8.7.2) nell'operazione prefisso. PutValuerichiede un riferimento mentre []come espressione da sola non produce un riferimento. Un'espressione contenente un riferimento variabile (diciamo che avevamo precedentemente definito, var a = []quindi ++afunziona) o l'accesso alla proprietà di un oggetto (come [[]][0]) produce un riferimento. In termini più semplici, l'operatore del prefisso non solo produce un valore, ma ha anche bisogno di un posto per inserirlo.
Tim Down

13
@pimvdb: Quindi dopo l'esecuzione var a = []; ++a, aè 1. Dopo l'esecuzione ++[[]][0], l'array creato [[]]dall'espressione ora contiene solo il numero 1 all'indice 0. ++richiede un riferimento per fare ciò.
Tim Down

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

Quindi abbiamo una concatenazione di stringhe

1+[0].toString() = 10

7
Non sarebbe più chiaro scrivere ===piuttosto che =>?
Mateen Ulhaq,

61

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

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

L'espressione ++[[]][+[]]+[+[]]può inizialmente sembrare piuttosto imponente e oscura, ma in realtà è relativamente facile da scomporre in espressioni separate. Di seguito ho semplicemente aggiunto le parentesi per chiarezza; Posso assicurarti che non cambiano nulla, ma se vuoi verificarlo, sentiti libero di informarti sull'operatore di raggruppamento . Quindi, l'espressione può essere scritta più chiaramente come

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

Abbattendo questo, possiamo semplificare osservando che +[]valuta 0. Per soddisfare se stessi perché questo è vero, controllare l' operatore unario + e seguire il percorso leggermente tortuoso che finisce con ToPrimitive convertire l'array vuoto in una stringa vuota, che viene infine convertito 0da ToNumber . Ora possiamo sostituire 0ogni istanza di +[]:

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

Già più semplice. Per quanto riguarda ++[[]][0], questa è una combinazione dell'operatore di incremento del prefisso ( ++), una matrice letterale che definisce una matrice con un singolo elemento che è essa stessa una matrice vuota ( [[]]) e un accessor di proprietà ( [0]) chiamato sulla matrice definita dalla matrice letterale.

Quindi, possiamo semplificare [[]][0]solo []e abbiamo ++[], giusto? In realtà, non è così perché la valutazione ++[]genera un errore, che inizialmente può sembrare confuso. Tuttavia, un piccolo pensiero sulla natura di ++questo chiarisce: viene utilizzato per incrementare una variabile (ad esempio ++i) o una proprietà dell'oggetto (ad esempio ++obj.count). Non solo valuta un valore, ma memorizza anche quel valore da qualche parte. Nel caso di ++[], non ha un posto dove mettere il nuovo valore (qualunque esso sia) perché non c'è alcun riferimento a una proprietà o variabile dell'oggetto da aggiornare. In termini di specifiche, questo è coperto dall'operazione PutValue interna , che viene chiamata dall'operatore di incremento del prefisso.

Allora, cosa fa ++[[]][0]? Bene, con una logica simile +[], l'array interno viene convertito in 0e questo valore viene incrementato 1per darci un valore finale di 1. Il valore della proprietà 0nella matrice esterna viene aggiornato 1e l'intera espressione viene valutata 1.

Questo ci lascia con

1 + [0]

... che è un semplice utilizzo dell'operatore addizione . Entrambi gli operandi vengono prima convertiti in primitivi e se uno dei due valori primitivi è una stringa, viene eseguita la concatenazione delle stringhe, altrimenti viene eseguita l'aggiunta numerica. [0]viene convertito in "0", quindi viene utilizzata la concatenazione di stringhe, producendo "10".

Infine, qualcosa che potrebbe non essere immediatamente evidente è che l'override di uno dei metodi toString()o cambierà il risultato dell'espressione, poiché entrambi vengono controllati e utilizzati se presenti durante la conversione di un oggetto in un valore primitivo. Ad esempio, quanto seguevalueOf()Array.prototype

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

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


24

Rendiamolo 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"

13

Questo valuta lo stesso ma un po 'più piccolo

+!![]+''+(+[])
  • [] - è un array convertito che viene convertito in 0 quando si aggiunge o si sottrae da esso, quindi + [] = 0
  • ! [] - restituisce false, quindi !! [] restituisce true
  • + !! [] - converte il vero in un valore numerico che restituisce vero, 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:

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

Beh no, continua a essere "10". Tuttavia, questo lo sta facendo in modo diverso. Prova a valutarlo in un ispettore javascript come chrome o qualcosa del genere.
Vlad Shlosberg,

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

Questo valuta lo stesso ma è anche più piccolo del tuo:"10"
ADJenks il

7

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

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


4

Forse i modi più brevi possibili per valutare un'espressione in "10" senza cifre sono:

+!+[] + [+[]] // "10"

-~[] + [+[]] // "10"

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

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

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

Quindi JS valuta l' espressione 1 + [0], quindi Number + Array. Quindi funziona la specifica ECMA: l' +operatore converte entrambi gli operandi in una stringa chiamando le toString()/valueOf()funzioni dalla baseObject prototipo di . Funziona come una funzione aggiuntiva se entrambi gli operandi di un'espressione sono solo numeri. Il trucco è che le matrici 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 Objectscomporta due aggiunte NaN:

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

1
  1. La stringa unaria più data viene convertita in numero
  2. La stringa data dall'operatore di incremento 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

1
La risposta è confusa / confusa, IOW sbagliato. non[] è equivalente a . Innanzitutto l'elemento viene estratto, quindi convertito da . ""++
Orecchie appuntite

1

Passo dopo passo di ciò, +trasforma il valore in un numero e se aggiungi a un array vuoto +[]... poiché è vuoto ed è uguale a0 , lo farà

Quindi da lì, ora guarda nel tuo codice, lo è ++[[]][+[]]+[+[]] ...

E c'è un vantaggio tra loro ++[[]][+[]]+[+[]]

Quindi questi [+[]]torneranno [0]in quanto hanno un array vuoto in cui viene convertito0 nell'altro array ...

Come immagina, il primo valore è un array bidimensionale con un array all'interno ... quindi [[]][+[]]sarà uguale al [[]][0]quale tornerà []...

E alla fine ++convertilo e aumentalo in 1...

Quindi puoi immaginare, 1+ "0"sarà "10"...

Perché restituisce la stringa "10"?

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.