Analizzare gli esempi JavaScript nel discorso “Wat” di Gary Bernhardt

Questo post è un omaggio al fantastico discorso “Wat” di Gary Bernhardt in cui sottolinea le peculiarità di alcuni costrutti linguistici in Ruby e JavaScript. Se non avete ancora visto il discorso, vi consiglio vivamente di prendere il tempo e fare proprio questo! È lungo solo circa 4 minuti e molto divertente, lo prometto.

Nel suo discorso, Gary mostra questi quattro frammenti di codice JavaScript:

Peculiarità in JavaScript

Vediamo molte parentesi, parentesi graffe e segni più. Ecco cosa valutano questi frammenti:

  • + == ""
  • + {} == ""
  • {} + == 0
  • {} + {} == NaN

Quando ho visto questi esempi per la prima volta, ho pensato: “Wow, che sembra disordinato!”I risultati possono sembrare incoerenti o addirittura arbitrari, ma portami qui. Tutti questi esempi sono in realtà molto coerenti e non così male come sembrano!

#Frammento #1: +

Iniziamo con il primo frammento:

 + // ""

Come possiamo vedere, applicando l’operatore + a due array vuoti si ottiene una stringa vuota. Questo perché la rappresentazione stringa di un array è la rappresentazione stringa di tutti i suoi elementi, concatenati insieme a virgole:

.toString()// "1,2,3".toString()// "1,2".toString()// "1".toString()// ""

Un array vuoto non contiene elementi, quindi la sua rappresentazione stringa è una stringa vuota. Pertanto, la concatenazione di due stringhe vuote è solo un’altra stringa vuota.

#Frammento #2: + {}

Finora, tutto bene. Esaminiamo ora il secondo frammento:

 + {}// ""

Si noti che poiché non abbiamo a che fare con due numeri, l’operatore + esegue ancora una volta la concatenazione delle stringhe anziché l’aggiunta di due valori numerici.

Nella sezione precedente, abbiamo già visto che la rappresentazione della stringa di un array vuoto è una stringa vuota. La rappresentazione stringa del letterale oggetto vuoto qui è il valore predefinito "". Anteponendo una stringa vuota non cambia il valore, quindi "" è il risultato finale.

In JavaScript, gli oggetti possono implementare un metodo speciale chiamato toString() che restituisce una rappresentazione stringa personalizzata dell’oggetto su cui viene chiamato il metodo. Il nostro letterale oggetto vuoto non implementa tale metodo, quindi stiamo tornando all’implementazione predefinita del prototipo Object.

#Frammento #3: {} +

Direi che finora i risultati non sono stati troppo inaspettati. Hanno semplicemente seguito le regole di tipo coercizione e rappresentazioni di stringhe predefinite in JavaScript.

Tuttavia, {} + è dove gli sviluppatori iniziano a confondersi:

{} + // 0

Perché vediamo 0(il numero zero) se digitiamo la riga sopra in un REPL JavaScript come la console del browser? Il risultato non dovrebbe essere una stringa, proprio come lo era + {}?

Prima di risolvere l’enigma, considerare i tre diversi modi in cui l’operatore + può essere utilizzato:

// 1) Addition of two numeric values2 + 2 == 4// 2) String concatenation of two values"2" + "2" == "22"// 3) Conversion of a value to a number+2 == 2+"2" == 2

Nei primi due casi, l’operatore + è un operatore binario perché ha due operandi (a sinistra e a destra). Nel terzo caso, l’operatore + è un operatore unario perché ha solo un singolo operando (a destra).

Considera anche i due possibili significati di {} in JavaScript. Di solito, scriviamo {} per indicare un oggetto vuoto letterale, ma se siamo in posizione di istruzione, la grammatica JavaScript specifica {} per indicare un blocco vuoto. Il seguente pezzo di codice definisce due blocchi vuoti, nessuno dei quali è un oggetto letterale:

{}// Empty block{ // Empty block}

diamo un’occhiata al nostro frammento di nuovo:

{} + 

mi permetta di modificare lo spazio un po ‘per rendere piu’ chiaro come il motore JavaScript vede il codice:

{ // Empty block}+;

Ora possiamo vedere chiaramente cosa sta succedendo qui. Abbiamo un’istruzione block seguita da un’altra istruzione che contiene un’espressione unaria + che opera su un array vuoto. Il punto e virgola finale viene inserito automaticamente secondo le regole di ASI (inserimento automatico del punto e virgola).

È possibile verificare facilmente nella console del browser che +valuta 0. L’array vuoto ha una stringa vuota come rappresentazione della stringa, che a sua volta viene convertita nel numero zero dall’operatore +. Infine, il valore dell’ultima istruzione (+, in questo caso) viene riportato dalla console del browser.

In alternativa, è possibile alimentare entrambi i frammenti di codice a un parser JavaScript come Esprima e confrontare gli alberi di sintassi astratti risultanti. Ecco l’AST per + {}:

{ "type": "Program", "body": }, "right": { "type": "ObjectExpression", "properties": } } } ], "sourceType": "script"}

Ed ecco l’AST per {} + :

{ "type": "Program", "body": }, { "type": "ExpressionStatement", "expression": { "type": "UnaryExpression", "operator": "+", "argument": { "type": "ArrayExpression", "elements": }, "prefix": true } } ], "sourceType": "script"}

La confusione deriva da una sfumatura della grammatica JavaScript che utilizza parentesi graffe sia per i letterali degli oggetti che per i blocchi. Nella posizione dell’istruzione, una parentesi graffa di apertura avvia un blocco, mentre nella posizione dell’espressione una parentesi graffa di apertura avvia un oggetto letterale.

#Frammento #4: {} + {}

Infine, diamo rapidamente un’occhiata al nostro ultimo frammento {} + {}:

{} + {}// NaN

Bene, aggiungere due oggetti letterali è letteralmente “non un numero” – ma stiamo aggiungendo due oggetti letterali qui? Non lasciate che le parentesi graffe ingannare di nuovo! Ecco cosa sta succedendo:

{ // Empty block}+{};

È praticamente lo stesso affare dell’esempio precedente. Tuttavia, ora stiamo applicando l’operatore unary plus a un oggetto vuoto letterale. Questo è fondamentalmente lo stesso di fare Number({}), che si traduce in NaN perché il nostro oggetto letterale non può essere convertito in un numero.

Se si desidera che il motore JavaScript analizzi il codice come due oggetti letterali vuoti, avvolgere il primo (o l’intero pezzo di codice) tra parentesi. Ora dovresti vedere il risultato atteso:

({}) + {}// ""({} + {})// ""

La parentesi di apertura fa sì che il parser tenti di riconoscere un’espressione, motivo per cui non tratta {} come un blocco (che sarebbe un’istruzione).

#Summary

Ora dovresti vedere perché i quattro frammenti di codice valutano il modo in cui lo fanno. Non è affatto arbitrario o casuale; le regole della coercizione di tipo sono applicate esattamente come stabilito nella specifica e nella grammatica della lingua.

Tieni presente che se una parentesi di apertura è il primo carattere ad apparire in un’istruzione, verrà interpretata come l’inizio di un blocco piuttosto che un oggetto letterale.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.