Ce este nou în ES6? Perspectiva unei conversii CoffeeScript
Publicat: 2022-03-11Sunt fan CoffeeScript de peste doi ani. Găsesc că sunt mai productiv când scriu CoffeeScript, fac mai puține greșeli prostești și, cu suport pentru hărți sursă, depanarea codului CoffeeScript este complet nedureroasă.
Recent, m-am jucat cu ES6 folosind Babel și, în general, sunt un fan. În acest articol, voi compila concluziile mele despre ES6 din perspectiva unui convertit CoffeeScript, voi privi lucrurile pe care le iubesc și voi vedea ce lucruri îmi mai lipsesc.
Indentare semnificativă din punct de vedere sintactic: o binefacere sau o nenorocire?
Întotdeauna am avut o relație de dragoste/ura cu indentarea semnificativă din punct de vedere sintactic a lui CoffeeScript. Când totul merge bine, primești „Uite mamă, fără mâini!” dreptul de lăudare de a folosi o astfel de sintaxă super-minimalistă. Dar când lucrurile merg prost și indentarea incorectă provoacă erori reale (credeți-mă, se întâmplă), nu se simte că beneficiile merită.
if iWriteCoffeeScript if iAmNotCareful badThings = 'happen'
De obicei, aceste erori sunt cauzate atunci când aveți un bloc de cod cu câteva instrucțiuni imbricate și indentați greșit o instrucțiune. Da, de obicei, este cauzat de ochii obosiți, dar este mult mai probabil să se întâmple în CoffeeScript, după părerea mea.
Când am început să scriu ES6, nu eram foarte sigur care ar fi reacția mea intestinală. S-a dovedit că mi s-a părut foarte plăcut să începi din nou să folosești bretele. Folosirea unui editor modern ajută, de asemenea, deoarece, în general, trebuie doar să deschideți bretele, iar editorul este destul de amabil să îl închidă pentru dvs. Mi s-a părut un pic ca să mă întorc într-un univers calm și clar după magia voodoo a CoffeeScript.
if (iWriteES6) { if (iWriteNestedStatements) { let badThings = 'are less likely to happen' } }
Desigur, insist să arunc punct și virgulă. Dacă nu avem nevoie de ele, eu zic să-i aruncăm afară. Le găsesc urâte și este o tastare suplimentară.
Suport de clasă
Suportul de clasă în ES6 este fantastic, iar dacă vă mutați de la CoffeeScript, vă veți simți ca acasă. Să comparăm sintaxa dintre cele două cu un exemplu simplu:
Clasa 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` } }
Clasa 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"
Prima impresie
Primul lucru pe care îl puteți observa este că ES6 este încă mult mai pronunțat decât CoffeeScript. O comandă rapidă care este foarte utilă în CoffeeScript este suportul pentru atribuirea automată a variabilelor de instanță în constructor:
constructor: (@numberOfLegs) ->
Acest lucru este echivalent cu următorul în ES6:
constructor(numberOfLegs) { this.numberOfLegs = numberOfLegs }
Desigur, acesta nu este chiar sfârșitul lumii și, dacă este ceva, această sintaxă mai explicită este mai ușor de citit. Un alt lucru mic care lipsește este că nu avem nicio scurtătură @
pentru this
, care s-a simțit întotdeauna plăcut venind dintr-un fundal Ruby, dar din nou nu este un deal-breaker masiv. În general, ne simțim aproape ca acasă aici și prefer de fapt sintaxa ES6 pentru definirea metodelor.
Ce Super!
Există o mică problemă cu modul în care este gestionat super
în ES6. CoffeeScript tratează super
în modul Ruby („te rugăm să trimiți un mesaj către metoda superclasei cu același nume”), dar singura dată când poți folosi un super „nud” în ES6 este în constructor. ES6 urmează o abordare mai convențională asemănătoare Java, în care super
este o referință la instanța superclasă.
Ma voi intoarce
În CoffeeScript putem folosi returnări implicite pentru a construi cod frumos ca următorul:
foo = -> if Math.random() > 0.5 if Math.random() > 0.5 if Math.random() > 0.5 "foo"
Aici aveți o șansă foarte mică de a primi „foo” înapoi atunci când apelați foo()
. ES6 nu are nimic din toate acestea și ne obligă să revenim folosind cuvântul cheie return
:
const foo = () => { if (Math.random() > 0.5 ) { if (Math.random() > 0.5 ) { if (Math.random() > 0.5 ) { return "foo" } } } }
După cum putem vedea în exemplul de mai sus, acesta este, în general, un lucru bun, dar tot am uitat să-l adaug și obțin profituri neașteptate nedefinite peste tot! Există o excepție pe care am întâlnit-o, în ceea ce privește funcțiile săgeată, în care obțineți un randament implicit pentru o linie ca aceasta:
array.map( x => x * 10 )
Acest lucru este oarecum util, dar poate fi ușor confuz, deoarece aveți nevoie de return
dacă adăugați bretele:
array.map( x => { return x * 10 })
Cu toate acestea, încă mai are sens. Raționamentul este că, dacă ați adăugat bretele este pentru că doriți să utilizați mai multe linii, iar dacă aveți mai multe linii, are sens să fie clar ce returnați.
Bonusuri pentru sintaxa clasei ES6
Am văzut că CoffeeScript are câteva trucuri sintactice în mânecă atunci când vine vorba de definirea claselor, dar ES6 are și câteva trucuri proprii.
Getters și Setters
ES6 are suport puternic pentru încapsulare prin gettere și setare, așa cum este ilustrat în exemplul următor:
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 } }
Datorită getterului, dacă accesăm bananaStore.bananas
va returna doar banane coapte. Acest lucru este foarte grozav, deoarece în CoffeeScript ar trebui să implementăm acest lucru printr-o metodă getter precum bananaStore.getBananas()
. Acest lucru nu pare deloc natural când în JavaScript suntem obișnuiți să accesăm proprietăți direct. De asemenea, face dezvoltarea confuză, deoarece trebuie să ne gândim pentru fiecare proprietate cum ar trebui să o accesăm - este .bananas
sau getBananas()
?

Setter-ul este la fel de util, deoarece putem curăța setul de date sau chiar putem arunca o excepție atunci când sunt setate date invalide, așa cum se arată în exemplul de jucărie de mai sus. Acest lucru va fi foarte familiar pentru oricine dintr-un fundal Ruby.
Personal, nu cred că asta înseamnă că ar trebui să înnebunești folosind getter și setter pentru orice. Dacă puteți câștiga ceva prin încapsularea variabilelor de instanță prin intermediul getters și setter-urilor (ca în exemplul de mai sus), atunci mergeți mai departe și faceți acest lucru. Dar imaginați-vă că aveți doar un steag boolean, cum ar fi isEditable
. Dacă nu ați pierde nimic prin expunerea directă a proprietății isEditable
, aș susține că aceasta este cea mai bună abordare, deoarece este cea mai simplă.
Metode statice
ES6 are suport pentru metode statice, ceea ce ne permite să facem acest truc obraznic:
class Foo { static new() { return new this } }
Acum, dacă urăști să scrii new Foo()
, poți să scrii Foo.new()
. Puriștii vor mormăi, deoarece new
este un cuvânt rezervat, dar după un test foarte rapid pare să funcționeze în Node.js și cu browserele moderne. Dar probabil că nu doriți să utilizați acest lucru în producție!
Din păcate, deoarece nu există suport pentru proprietățile statice în ES6, dacă doriți să definiți constante de clasă și să le accesați în metoda dvs. statică, ar trebui să faceți ceva de genul acesta, care este puțin hacker:
class Foo { static imgPath() { return `${this.ROOT_PATH}/img` } } Foo.ROOT_PATH = '/foo'
Există o propunere ES7 de a implementa proprietăți declarative de instanță și clasă, astfel încât să putem face ceva de genul acesta, ceea ce ar fi frumos:
class Foo { static ROOT_PATH = '/foo' static imgPath() { return `${this.ROOT_PATH}/img` } }
Acest lucru se poate face deja destul de elegant în CoffeeScript:
class Foo @ROOT_PATH: '/foo' @imgPath: -> @ROOT_PATH
Interpolarea șirurilor
A sosit momentul când putem face în sfârșit interpolarea șirurilor în Javascript!
`I am so happy that in the year ${new Date().getFullYear()} we can interpolate strings`
Venind de la CoffeeScript/Ruby, această sintaxă se simte puțin uk. Poate din cauza fundalului meu Ruby, unde este folosit un backtick pentru a executa comenzile de sistem, la început se pare destul de greșit. De asemenea, folosirea semnului dolarului pare un fel de optzeci – dar poate că sunt doar eu.
Presupun că, din cauza problemelor de compatibilitate inversă, pur și simplu nu a fost posibil să se implementeze interpolarea șirurilor în stil CoffeeScript. "What a #{expletive} shame"
.
Funcții săgeți
Funcțiile săgeților sunt luate de la sine înțelese în CoffeeScripter. ES6 a implementat aproape sintaxa săgeată grăsime a CoffeeScript. Așadar, obținem o sintaxă scurtă și drăguță și obținem domeniul lexical this
, așa că nu trebuie să sărim prin cercuri ca acesta când folosim ES5:
var that = this doSomethingAsync().then( function(res) { that.foo(res) })
Echivalentul în ES6 este:
doSomethingAsync().then( res => { this.foo(res) })
Observați cum am omis paranteza din jurul apelului unar, pentru că pot!
Literale obiect pe steroizi
Literale obiectelor din ES6 au unele îmbunătățiri serioase de performanță. Ei pot face tot felul de lucruri în aceste zile!
Nume de proprietăți dinamice
Până când am scris acest articol, nu mi-am dat seama că, începând cu CoffeeScript 1.9.1, acum putem face asta:
dynamicProperty = 'foo' obj = {"#{dynamicProperty}": 'bar'}
Ceea ce este mult mai puțin dureros decât ceva de genul acesta care era necesar înainte:
dynamicProperty = 'foo' obj = {} obj[dynamicProperty] = 'bar'
ES6 are o sintaxă alternativă care cred că este destul de drăguță, dar nu un câștig uriaș:
let dynamicProperty = 'foo' let obj = { [dynamicProperty]: 'bar' }
Comenzi rapide funky
Acesta este de fapt preluat din CoffeeScript, dar a fost ceva despre care nu am știut până acum. Este foarte util oricum:
let foo = 'foo' let bar = 'bar' let obj = { foo, bar }
Acum, conținutul obj este { foo: 'foo', bar: 'bar' }
. Este foarte util și merită amintit.
Tot ce poate face o clasă și eu pot face!
Literale obiect pot face acum cam tot ce poate face o clasă, chiar și ceva de genul:
let obj = { _foo: 'foo', get foo() { return this._foo }, set foo(str) { this._foo = str }, isFoo() { return this.foo === 'foo' } }
Nu sunt sigur de ce ai vrea să începi să faci asta, dar acum poți! Desigur, nu poți defini metode statice... deoarece asta nu ar avea deloc sens.
Buclele pentru a vă încurca
Sintaxa for-loop ES6 va introduce câteva erori de memorie musculară pentru cei cu experiență în CoffeeScript, deoarece for-of a lui ES6 se traduce prin for-in a CoffeeSCript și 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
Ar trebui să trec la ES6?
În prezent, am lucrat la un proiect Node.js destul de mare folosind 100% CoffeeScript și încă sunt foarte fericit și super productiv cu el. Aș spune că singurul lucru pe care sunt cu adevărat gelos în ES6 sunt getters și setters.
De asemenea, este încă puțin dureros să folosiți ES6 în practică astăzi. Dacă puteți utiliza cea mai recentă versiune Node.js, atunci obțineți aproape toate caracteristicile ES6, dar pentru versiunile mai vechi și în browser lucrurile sunt încă mai puțin roz. Da, poți folosi Babel, dar, desigur, asta înseamnă integrarea lui Babel în stiva ta.
Acestea fiind spuse, pot vedea ES6 în următorii doi ani câștigând mult teren și sper să văd lucruri și mai mari în ES7.
Ceea ce sunt cu adevărat mulțumit de ES6 este că „vachiul JavaScript simplu” este aproape la fel de prietenos și puternic de la început ca CoffeeScript. Acest lucru este grozav pentru mine ca freelancer – în trecut obișnuiam să lucrez la proiecte JavaScript – dar cu ES6 totul pare puțin mai strălucitor.