Cosa c'è di nuovo in ES6? Prospettiva di una conversione CoffeeScript
Pubblicato: 2022-03-11Sono un fan di CoffeeScript da oltre due anni. Trovo di essere più produttivo nello scrivere CoffeeScript, di fare meno errori stupidi e con il supporto per le mappe dei sorgenti il debug del codice CoffeeScript è completamente indolore.
Recentemente ho giocato con ES6 usando Babel e nel complesso sono un fan. In questo articolo, compilerò le mie scoperte su ES6 dal punto di vista di un convertito CoffeeScript, guarderò le cose che amo e vedrò quali cose mi mancano ancora.
Indentazione sintatticamente significativa: un vantaggio o una rovina?
Ho sempre avuto un po' di rapporto di amore/odio con il rientro sintatticamente significativo di CoffeeScript. Quando tutto va bene, ricevi il messaggio "Guarda ma, niente mani!" vantarsi di usare una sintassi così superminimalista. Ma quando le cose vanno storte e un rientro errato causa veri bug (credetemi, succede), non sembra che i vantaggi valgano la pena.
if iWriteCoffeeScript if iAmNotCareful badThings = 'happen'
In genere questi bug sono causati quando si dispone di un blocco di codice con alcune istruzioni nidificate e si indenta accidentalmente un'istruzione in modo errato. Sì, di solito è causato da occhi stanchi, ma secondo me è molto più probabile che accada in CoffeeScript.
Quando ho iniziato a scrivere ES6 non ero del tutto sicuro di quale sarebbe stata la mia reazione istintiva. Si scopre che in realtà è stato davvero bello ricominciare a usare le parentesi graffe. Anche l'uso di un editor moderno aiuta, poiché generalmente devi solo aprire il parentesi graffa e il tuo editor è così gentile da chiuderlo per te. È stato un po' come tornare in un universo calmo e limpido dopo la magia voodoo di CoffeeScript.
if (iWriteES6) { if (iWriteNestedStatements) { let badThings = 'are less likely to happen' } }
Ovviamente insisto per eliminare il punto e virgola. Se non ci servono, dico di buttarli fuori. Li trovo brutti ed è una digitazione extra.
Supporto di classe
Il supporto per le lezioni in ES6 è fantastico e se stai passando da CoffeeScript ti sentirai come a casa. Confrontiamo la sintassi tra i due con un semplice esempio:
Classe ES6
class Animal { constructor(numberOfLegs) { this.numberOfLegs = numberOfLegs } toString() { return `I am an animal with ${this.numberOfLegs} legs` } } class Monkey extends Animal { constructor(bananas) { super(2) this.bananas = bananas } toString() { let superString = super.toString() .replace(/an animal/, 'a monkey') return `${superString} and ${this.bananas} bananas` } }
Classe CoffeeScript
class Animal constructor: (@numberOfLegs) -> toString: -> "I am an animal with #{@numberOfLegs} legs" class Monkey extends Animal constructor: (@numberOfBananas) -> super(2) toString: -> superString = super.toString() .replace(/an animal/, 'a monkey') "#{superString} and #{@numberOfLegs} bananas"
Prime impressioni
La prima cosa che potresti notare è che ES6 è ancora molto più dettagliato di CoffeeScript. Una scorciatoia molto utile in CoffeeScript è il supporto per l'assegnazione automatica delle variabili di istanza nel costruttore:
constructor: (@numberOfLegs) ->
Ciò equivale a quanto segue in ES6:
constructor(numberOfLegs) { this.numberOfLegs = numberOfLegs }
Ovviamente non è proprio la fine del mondo, e semmai questa sintassi più esplicita è più facile da leggere. Un'altra piccola cosa che manca è che non abbiamo @
scorciatoia per this
, che è sempre stato bello provenendo da uno sfondo Ruby, ma ancora una volta non è un enorme rompicapo. In generale ci sentiamo abbastanza a nostro agio qui, e in realtà preferisco la sintassi ES6 per definire i metodi.
Che Super!
C'è un piccolo problema con il modo in cui il super
viene gestito in ES6. CoffeeScript gestisce super
in modo Ruby ("per favore invia un messaggio al metodo della superclasse con lo stesso nome"), ma l'unica volta in cui puoi usare un super "nudo" in ES6 è nel costruttore. ES6 segue un approccio più convenzionale simile a Java in cui super
è un riferimento all'istanza della superclasse.
Ritornerò
In CoffeeScript possiamo usare i ritorni impliciti per creare codice bellissimo come il seguente:
foo = -> if Math.random() > 0.5 if Math.random() > 0.5 if Math.random() > 0.5 "foo"
Qui hai una possibilità molto piccola di recuperare "foo" quando chiami foo()
. ES6 non ha nulla di tutto ciò e ci costringe a tornare utilizzando la parola chiave return
:
const foo = () => { if (Math.random() > 0.5 ) { if (Math.random() > 0.5 ) { if (Math.random() > 0.5 ) { return "foo" } } } }
Come possiamo vedere nell'esempio sopra, questa è generalmente una buona cosa, ma mi ritrovo ancora a dimenticare di aggiungerlo e a ricevere ritorni inaspettati e indefiniti dappertutto! C'è un'eccezione in cui mi sono imbattuto, riguardante le funzioni freccia, in cui ottieni un ritorno implicito per una fodera come questa:
array.map( x => x * 10 )
Questo è un po 'utile ma può creare confusione poiché è necessario il return
se si aggiungono le parentesi graffe:
array.map( x => { return x * 10 })
Tuttavia, ha ancora senso. Il ragionamento è che se hai aggiunto le parentesi graffe è perché vuoi usare più righe e se hai più righe ha senso essere chiaro cosa stai restituendo.
Bonus di sintassi di classe ES6
Abbiamo visto che CoffeeScript ha alcuni trucchi sintattici nella manica quando si tratta di definire le classi, ma ES6 ha anche alcuni trucchi propri.
Getter e Setter
ES6 ha un potente supporto per l'incapsulamento tramite getter e setter, come illustrato nel seguente esempio:
class BananaStore { constructor() { this._bananas = [] } populate() { // fetch our bananas from the backend here } get bananas() { return this._bananas.filter( banana => banana.isRipe ) } set bananas(bananas) { if (bananas.length > 100) { throw `Wow ${bananas.length} is a lot of bananas!` } this._bananas = bananas } }
Grazie al getter, se accediamo a bananaStore.bananas
restituirà solo banane mature. Questo è davvero fantastico poiché in CoffeeScript dovremmo implementarlo tramite un metodo getter come bananaStore.getBananas()
. Questo non sembra del tutto naturale quando in JavaScript siamo abituati ad accedere direttamente alle proprietà. Rende anche lo sviluppo confuso perché dobbiamo pensare per ogni proprietà come dovremmo accedervi: è .bananas
o getBananas()
?

Il setter è ugualmente utile in quanto possiamo ripulire il set di dati o persino generare un'eccezione quando vengono impostati dati non validi, come mostrato nell'esempio del giocattolo sopra. Questo sarà molto familiare a chiunque provenga da un background Ruby.
Personalmente non penso che questo significhi che dovresti impazzire usando getter e setter per tutto. Se puoi ottenere qualcosa incapsulando le tue variabili di istanza tramite getter e setter (come nell'esempio sopra), vai avanti e fallo. Ma immagina di avere solo un flag booleano come isEditable
. Se non perdessi nulla esponendo direttamente la proprietà isEditable
, direi che è l'approccio migliore, in quanto è il più semplice.
Metodi statici
ES6 ha il supporto per i metodi statici, che ci permette di fare questo trucco birichino:
class Foo { static new() { return new this } }
Ora, se odi scrivere new Foo()
ora puoi scrivere Foo.new()
. I puristi si lamenteranno poiché new
è una parola riservata, ma dopo un test molto rapido sembra funzionare in Node.js e con i browser moderni perfettamente. Ma probabilmente non vuoi usarlo in produzione!
Sfortunatamente, poiché non c'è supporto per le proprietà statiche in ES6, se vuoi definire le costanti di classe e accedervi nel tuo metodo statico dovresti fare qualcosa del genere, che è un po' hackish:
class Foo { static imgPath() { return `${this.ROOT_PATH}/img` } } Foo.ROOT_PATH = '/foo'
C'è una proposta ES7 per implementare l'istanza dichiarativa e le proprietà della classe, in modo che possiamo fare qualcosa del genere, il che sarebbe bello:
class Foo { static ROOT_PATH = '/foo' static imgPath() { return `${this.ROOT_PATH}/img` } }
Questo può già essere fatto in modo abbastanza elegante in CoffeeScript:
class Foo @ROOT_PATH: '/foo' @imgPath: -> @ROOT_PATH
Interpolazione di stringhe
È giunto il momento in cui possiamo finalmente eseguire l'interpolazione di stringhe in Javascript!
`I am so happy that in the year ${new Date().getFullYear()} we can interpolate strings`
Proveniente da CoffeeScript/Ruby, questa sintassi sembra un po' schifosa. Forse a causa del mio background su Ruby in cui viene utilizzato un backtick per eseguire i comandi di sistema, all'inizio sembra piuttosto sbagliato. Anche usare il simbolo del dollaro sembra un po' anni ottanta, ma forse sono solo io.
Suppongo che a causa di problemi di compatibilità con le versioni precedenti non fosse possibile implementare l'interpolazione di stringhe in stile CoffeeScript. "What a #{expletive} shame"
.
Funzioni delle frecce
Le funzioni delle frecce sono date per scontate in CoffeeScripter. ES6 ha praticamente implementato la sintassi della freccia grassa di CoffeeScript. Quindi otteniamo una bella sintassi breve e otteniamo l'ambito lessicale this
, quindi non dobbiamo saltare attraverso i cerchi in questo modo quando si utilizza ES5:
var that = this doSomethingAsync().then( function(res) { that.foo(res) })
L'equivalente in ES6 è:
doSomethingAsync().then( res => { this.foo(res) })
Nota come ho tralasciato la parentesi attorno alla richiamata unaria, perché posso!
Letterali oggetto su steroidi
I valori letterali oggetto in ES6 hanno alcuni seri miglioramenti delle prestazioni. Possono fare ogni genere di cose in questi giorni!
Nomi di proprietà dinamici
In realtà non mi sono reso conto fino a quando non ho scritto questo articolo che da CoffeeScript 1.9.1 ora possiamo fare questo:
dynamicProperty = 'foo' obj = {"#{dynamicProperty}": 'bar'}
Che è molto meno doloroso di qualcosa del genere che era necessario prima:
dynamicProperty = 'foo' obj = {} obj[dynamicProperty] = 'bar'
ES6 ha una sintassi alternativa che penso sia piuttosto carina, ma non una grande vittoria:
let dynamicProperty = 'foo' let obj = { [dynamicProperty]: 'bar' }
Scorciatoie funky
Questo è in realtà preso da CoffeeScript, ma fino ad ora era qualcosa che ignoravo. È comunque molto utile:
let foo = 'foo' let bar = 'bar' let obj = { foo, bar }
Ora il contenuto di obj è { foo: 'foo', bar: 'bar' }
. È super utile e vale la pena ricordarlo.
Tutto quello che una classe può fare anch'io!
Gli oggetti letterali ora possono fare praticamente tutto ciò che una classe può fare, anche qualcosa come:
let obj = { _foo: 'foo', get foo() { return this._foo }, set foo(str) { this._foo = str }, isFoo() { return this.foo === 'foo' } }
Non sono sicuro del motivo per cui vorresti iniziare a farlo, ma ehi, ora puoi! Ovviamente non è possibile definire metodi statici... poiché ciò non avrebbe alcun senso.
For-loop per confonderti
La sintassi for-loop di ES6 introdurrà alcuni bei bug di memoria muscolare per CoffeeScripter esperti, poiché il for-of di ES6 si traduce in for-in di CoffeeSCript e viceversa.
ES6
for (let i of [1, 2, 3]) { console.log(i) } // 1 // 2 // 3
CoffeeScript
for i of [1, 2, 3] console.log(i) # 0 # 1 # 2
Devo passare a ES6?
Attualmente sto lavorando a un progetto Node.js abbastanza grande utilizzando CoffeeScript al 100% e ne sono ancora molto felice e super produttivo. Direi che l'unica cosa di cui sono davvero geloso in ES6 sono i getter e i setter.
Inoltre è ancora leggermente doloroso usare ES6 in pratica oggi. Se sei in grado di utilizzare l'ultima versione di Node.js, ottieni quasi tutte le funzionalità di ES6, ma per le versioni precedenti e nel browser le cose sono ancora meno rosee. Sì, puoi usare Babel, ma ovviamente ciò significa integrare Babel nel tuo stack.
Detto questo, posso vedere ES6 nel prossimo anno o due guadagnare molto terreno e spero di vedere cose ancora più grandi in ES7.
Quello che mi fa davvero piacere con ES6 è che il "semplice vecchio JavaScript" è quasi intuitivo e potente come CoffeeScript. Questo è fantastico per me come libero professionista – in passato ero un po' contrario a lavorare su progetti JavaScript – ma con ES6 tutto sembra un po' più brillante.