C'è coercizione nel mondo JavaScript: una storia da detective
Nathan, non hai idea di cosa hai scoperto.
Lo sto studiando da settimane ormai. Tutto è iniziato in una notte di tempesta lo scorso ottobre. Mi sono imbattuto per caso nella Numberclasse - voglio dire, perché nel mondo JavaScript aveva una Numberclasse?
Non ero preparato per quello che avrei scoperto dopo.
Si scopre che JavaScript, senza dirtelo, ha cambiato i tuoi numeri in oggetti e i tuoi oggetti in numeri proprio sotto il tuo naso.
JavaScript sperava che nessuno potesse attecchire, ma le persone hanno segnalato strani comportamenti inaspettati, e ora grazie a te e alla tua domanda ho le prove che devo far esplodere questa cosa.
Questo è quello che abbiamo scoperto finora. Non so se dovrei anche dirtelo - potresti voler disattivare JavaScript.
> function dis() { return this }
undefined
Quando hai creato quella funzione, probabilmente non avevi idea di cosa sarebbe successo dopo. Tutto sembrava a posto, e tutto andava bene, per ora.
Nessun messaggio di errore, solo la parola "indefinita" nell'output della console, esattamente quello che ti aspetteresti. Dopotutto, questa era una dichiarazione di funzione - non dovrebbe restituire nulla.
Ma questo era solo l'inizio. Quello che è successo dopo, nessuno avrebbe potuto prevedere.
> five = dis.call(5)
Number {[[PrimitiveValue]]: 5}
Sì, lo so, ti aspettavi un 5, ma non è quello che hai, è stato - hai qualcos'altro - qualcosa di diverso.
Mi è successa la stessa cosa.
Non sapevo cosa farsene. Mi ha fatto impazzire. Non riuscivo a dormire, non potevo mangiare, ho provato a berlo via, ma nessuna quantità di Mountain Dew mi avrebbe fatto dimenticare. Non aveva alcun senso!
Fu allora che scoprii cosa stava realmente succedendo: era la coercizione e stava accadendo proprio lì davanti ai miei occhi, ma ero troppo cieco per vederlo.
Mozilla ha cercato di seppellirlo mettendolo dove sapevano che nessuno avrebbe guardato: la loro documentazione .
Dopo ore di lettura, rilettura e rilettura ricorsiva, ho trovato questo:
"... e i valori primitivi verranno convertiti in oggetti."
Era proprio lì come può essere spiegato con il font Open Sans. Era la call()funzione - come potrei essere così stupido ?!
Il mio numero non era più un numero. Nel momento in cui l'ho passato call(), è diventato qualcos'altro. È diventato ... un oggetto.
All'inizio non ci potevo credere. Come potrebbe essere vero? Ma non potevo ignorare le prove che si stavano accumulando intorno a me. È proprio lì se guardi solo:
> five.wtf = 'potato'
"potato"
> five.wtf
"potato"
wtfera giusto. I numeri non possono avere proprietà personalizzate: lo sappiamo tutti! È la prima cosa che ti insegnano all'Accademia.
Avremmo dovuto sapere il momento in cui abbiamo visto l'output della console: questo non era il numero che pensavamo fosse. Questo era un impostore - un oggetto che si spacciava come il nostro dolce numero innocente.
Questo è stato ... new Number(5).
Ovviamente! Aveva perfettamente senso. call()aveva un lavoro da svolgere, doveva invocare una funzione, e per farlo doveva popolarlo this, sapeva di non poterlo fare con un numero - aveva bisogno di un oggetto ed era disposto a fare qualsiasi cosa per ottenerlo, anche se ciò significava forzare il nostro numero. Quando ha call()visto il numero 5, ha visto un'opportunità.
Era il piano perfetto: aspettate che nessuno lo guardasse e scambiate il nostro numero con un oggetto che assomigli. Otteniamo un numero, la funzione viene invocata e nessuno sarebbe più saggio.
Era davvero il piano perfetto, ma come tutti i piani, anche quelli perfetti, c'era un buco in esso e stavamo per cadere proprio in esso.
Vedi, quello call()che non capiva era che non era l'unico in città in grado di forzare i numeri. Dopotutto questo era JavaScript: la coercizione era ovunque.
call() ho preso il mio numero e non mi sarei fermato fino a quando non avessi tolto la maschera dal suo piccolo impostore e lo avrei esposto all'intera comunità di Stack Overflow.
Ma come? Avevo bisogno di un piano. Certo sembra un numero, ma so che non lo è, deve esserci un modo per dimostrarlo. Questo è tutto! Esso si presenta come un numero, ma può agire come uno?
Ho detto che fiveho bisogno che diventi 5 volte più grande - non ha chiesto perché e non ho spiegato. Allora ho fatto quello che avrebbe fatto un buon programmatore: ho moltiplicato. Sicuramente non c'era modo di farsene uscire.
> five * 5
25
> five.wtf
'potato'
Accidenti! Non solo fivemoltiplicare, ma andava bene wtf. Accidenti a questo ragazzo e alla sua patata.
Cosa diavolo stava succedendo? Ho sbagliato su tutto questo? È fivedavvero un numero? No, devo mancare qualcosa, lo so, c'è qualcosa che devo dimenticare, qualcosa di così semplice ed essenziale che lo sto completamente trascurando.
Non stava andando bene, avevo scritto questa risposta per ore e non ero ancora più vicino a esprimere il mio punto. Non riuscivo a continuare così, alla fine la gente smetteva di leggere, dovevo pensare a qualcosa e dovevo pensarci velocemente.
Aspetta e basta! fivenon era 25, 25 era il risultato, 25 era un numero completamente diverso. Certo, come potrei dimenticare? I numeri sono immutabili. Quando si moltiplica 5 * 5nulla viene assegnato a qualsiasi cosa si crea semplicemente un nuovo numero 25.
Deve essere quello che sta succedendo qui. In qualche modo, quando moltiplico five * 5, fivedevo essere forzato in un numero e quel numero deve essere quello usato per la moltiplicazione. Sono i risultati di quella moltiplicazione che vengono stampati sulla console, non il valore di fivese stesso. fivenon viene mai assegnato nulla, quindi ovviamente non cambia.
Quindi, come posso fiveassegnarmi il risultato di un'operazione? Capito. Prima fiveancora di avere la possibilità di pensare, ho urlato "++".
> five++
5
Aha! L'ho avuto! Tutti sanno 5 + 1è 6, questa è stata la prova che mi serviva per esporre che fivenon era un numero! Era un impostore! Un cattivo impostore che non sapeva contare. E potrei provarlo. Ecco come agisce un numero reale:
> num = 5
5
> num++
5
Aspettare? Cosa stava succedendo qui? sospiro Sono stato così preso dal trambusto fiveche mi dimentico di come funzionano gli operatori postali. Quando uso il ++alla fine fivesto dicendo restituire il valore corrente, quindi incrementare five. È il valore prima dell'operazione che viene stampato sulla console. numera in effetti 6e ho potuto dimostrarlo:
>num
6
È ora di vedere cosa fivefosse davvero:
>five
6
... era esattamente come dovrebbe essere. fiveera buono, ma io ero meglio. Se fivefosse ancora un oggetto, ciò significherebbe che avrebbe ancora la proprietà wtfe io avrei voluto scommettere tutto ciò che non aveva.
> five.wtf
undefined
Aha! Avevo ragione. L'ho avuto! fiveera un numero ora - non era più un oggetto. Sapevo che il trucco della moltiplicazione non l'avrebbe salvato questa volta. Vedere five++è davvero five = five + 1. A differenza della moltiplicazione, l' ++operatore assegna un valore a five. Più specificamente, gli assegna i risultati dei five + 1quali proprio come nel caso della moltiplicazione restituisce un nuovo numero immutabile .
Sapevo di averlo, e solo per essere sicuro che non potesse evitarlo. Ho avuto un altro test nella manica. Se avessi ragione, ed fiveera davvero un numero ora, allora questo non avrebbe funzionato:
> five.wtf = 'potato?'
'potato?'
Questa volta non mi avrebbe ingannato. Sapevo che potato?sarebbe stato stampato sulla console perché è l'output del compito. La vera domanda è: ci sarà wtfancora?
> five.wtf
undefined
Proprio come sospettavo - niente - perché ai numeri non è possibile assegnare proprietà. Abbiamo imparato che il primo anno all'Accademia;)
Grazie Nathan. Grazie al tuo coraggio nel porre questa domanda, posso finalmente lasciarmi tutto alle spalle e passare a un nuovo caso.
Come questo sulla funzione toValue(). Oh caro dio. Nooo!
++sembra influenzare il tipo sottostante