O que há de novo no ES6? Perspectiva de um CoffeeScript Convert
Publicados: 2022-03-11Sou fã do CoffeeScript há mais de dois anos. Acho que sou mais produtivo escrevendo CoffeeScript, cometo menos erros bobos e, com suporte para mapas de origem, depurar o código CoffeeScript é completamente indolor.
Recentemente tenho jogado com ES6 usando Babel, e no geral sou fã. Neste artigo, vou compilar minhas descobertas no ES6 da perspectiva de um convertido em CoffeeScript, observar as coisas que eu amo e ver o que ainda estou perdendo.
Recuo sintaticamente significativo: um benefício ou uma maldição?
Eu sempre tive uma relação de amor/ódio com o recuo sintaticamente significativo do CoffeeScript. Quando tudo vai bem, você recebe o "Olha mãe, sem mãos!" direitos de se gabar de usar uma sintaxe tão superminimalista. Mas quando as coisas dão errado e o recuo incorreto causa bugs reais (confie em mim, isso acontece), não parece que os benefícios valem a pena.
if iWriteCoffeeScript if iAmNotCareful badThings = 'happen'
Normalmente, esses bugs são causados quando você tem um bloco de código com algumas instruções aninhadas e acidentalmente recua uma instrução incorretamente. Sim, geralmente é causado por olhos cansados, mas é muito mais provável que aconteça no CoffeeScript na minha opinião.
Quando comecei a escrever ES6 eu não tinha certeza de qual seria minha reação instintiva. Acontece que realmente foi muito bom começar a usar chaves novamente. Usar um editor moderno também ajuda, pois geralmente você só precisa abrir a chave e seu editor é gentil o suficiente para fechá-lo para você. Parecia um pouco como retornar a um universo calmo e claro após a magia vodu do CoffeeScript.
if (iWriteES6) { if (iWriteNestedStatements) { let badThings = 'are less likely to happen' } }
Claro que insisto em jogar fora os pontos e vírgulas. Se não precisarmos deles, eu digo para jogá-los fora. Acho-os feios e é digitação extra.
Suporte de classe
O suporte de classe no ES6 é fantástico e, se você estiver migrando do CoffeeScript, se sentirá em casa. Vamos comparar a sintaxe entre os dois com um exemplo simples:
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"
Primeiras impressões
A primeira coisa que você pode notar é que o ES6 ainda é muito mais detalhado que o CoffeeScript. Um atalho muito útil no CoffeeScript é o suporte para atribuição automática de variáveis de instância no construtor:
constructor: (@numberOfLegs) ->
Isso é equivalente ao seguinte no ES6:
constructor(numberOfLegs) { this.numberOfLegs = numberOfLegs }
É claro que isso não é realmente o fim do mundo, e essa sintaxe mais explícita é mais fácil de ler. Outra pequena coisa que está faltando é que não temos @
atalho para this
, o que sempre foi bom vindo de um background Ruby, mas novamente não é um grande problema. Em geral, nos sentimos em casa aqui, e eu prefiro a sintaxe ES6 para definir métodos.
Que super!
Há uma pequena pegadinha com a maneira como o super
é tratado no ES6. CoffeeScript lida com super
no estilo Ruby (“por favor, envie uma mensagem para o método da superclasse de mesmo nome”), mas a única vez que você pode usar um super “naked” no ES6 é no construtor. O ES6 segue uma abordagem mais convencional do tipo Java, onde super
é uma referência à instância da superclasse.
Eu Voltarei
No CoffeeScript podemos usar retornos implícitos para construir um belo código como o seguinte:
foo = -> if Math.random() > 0.5 if Math.random() > 0.5 if Math.random() > 0.5 "foo"
Aqui você tem uma chance muito pequena de obter “foo” de volta ao chamar foo()
. O ES6 não tem nada disso e nos obriga a retornar usando a palavra-chave return
:
const foo = () => { if (Math.random() > 0.5 ) { if (Math.random() > 0.5 ) { if (Math.random() > 0.5 ) { return "foo" } } } }
Como podemos ver no exemplo acima, isso geralmente é uma coisa boa, mas ainda me vejo esquecendo de adicioná-lo e obtendo retornos indefinidos inesperados em todo o lugar! Há uma exceção que encontrei, em relação às funções de seta, onde você obtém um retorno implícito para um forro como este:
array.map( x => x * 10 )
Isso é meio útil, mas pode ser um pouco confuso, pois você precisa do return
se adicionar as chaves:
array.map( x => { return x * 10 })
No entanto, ainda faz sentido. O raciocínio é que, se você adicionou as chaves, é porque deseja usar várias linhas e, se tiver várias linhas, faz sentido deixar claro o que está retornando.
Bônus de Sintaxe de Classe ES6
Vimos que CoffeeScript tem alguns truques sintáticos na manga quando se trata de definir classes, mas o ES6 também tem alguns truques próprios.
Getters e Setters
O ES6 tem suporte poderoso para encapsulamento por meio de getters e setters, conforme ilustrado no exemplo a seguir:
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 } }
Graças ao getter, se bananaStore.bananas
ele retornará apenas bananas maduras. Isso é realmente ótimo, pois no CoffeeScript precisaríamos implementar isso por meio de um método getter como bananaStore.getBananas()
. Isso não parece nada natural quando em JavaScript estamos acostumados a acessar propriedades diretamente. Também torna o desenvolvimento confuso porque precisamos pensar para cada propriedade como devemos acessá-la - é .bananas
ou getBananas()
?

O setter é igualmente útil, pois podemos limpar o conjunto de dados ou até mesmo lançar uma exceção quando dados inválidos são definidos, conforme mostrado no exemplo do brinquedo acima. Isso será muito familiar para qualquer pessoa com experiência em Ruby.
Eu pessoalmente não acho que isso significa que você deve enlouquecer usando getters e setters para tudo. Se você puder ganhar algo encapsulando suas variáveis de instância por meio de getters e setters (como no exemplo acima), vá em frente e faça isso. Mas imagine que você tenha apenas um sinalizador booleano como isEditable
. Se você não perderia nada expondo diretamente a propriedade isEditable
, eu diria que essa é a melhor abordagem, pois é a mais simples.
Métodos estáticos
O ES6 tem suporte para métodos estáticos, o que nos permite fazer esse truque impertinente:
class Foo { static new() { return new this } }
Agora, se você odeia escrever new Foo()
, agora pode escrever Foo.new()
. Os puristas vão reclamar, já que new
é uma palavra reservada, mas depois de um teste muito rápido, parece funcionar no Node.js e com navegadores modernos muito bem. Mas você provavelmente não quer usar isso na produção!
Infelizmente, porque não há suporte para propriedades estáticas no ES6, se você quiser definir constantes de classe e acessá-las em seu método estático, você teria que fazer algo assim, o que é um pouco hack:
class Foo { static imgPath() { return `${this.ROOT_PATH}/img` } } Foo.ROOT_PATH = '/foo'
Existe uma Proposta ES7 para implementar propriedades declarativas de instância e classe, para que possamos fazer algo assim, o que seria legal:
class Foo { static ROOT_PATH = '/foo' static imgPath() { return `${this.ROOT_PATH}/img` } }
Isso já pode ser feito de forma bastante elegante no CoffeeScript:
class Foo @ROOT_PATH: '/foo' @imgPath: -> @ROOT_PATH
Interpolação de String
Chegou a hora de finalmente podermos fazer interpolação de strings em Javascript!
`I am so happy that in the year ${new Date().getFullYear()} we can interpolate strings`
Vindo do CoffeeScript/Ruby, essa sintaxe parece um pouco yuk. Talvez por causa da minha experiência em Ruby onde um backtick é usado para executar comandos do sistema, parece muito errado no começo. Também usar o cifrão parece meio dos anos oitenta – mas talvez seja só eu.
Suponho que devido a preocupações de compatibilidade com versões anteriores, simplesmente não foi possível implementar a interpolação de strings no estilo CoffeeScript. "What a #{expletive} shame"
.
Funções de seta
As funções de seta são tidas como garantidas no CoffeeScripter. O ES6 praticamente implementou a sintaxe de seta gorda do CoffeeScript. Assim, obtemos uma sintaxe curta e agradável, e obtemos o escopo lexical this
, para que não precisemos passar por aros como este ao usar o ES5:
var that = this doSomethingAsync().then( function(res) { that.foo(res) })
O equivalente em ES6 é:
doSomethingAsync().then( res => { this.foo(res) })
Observe como deixei de fora os parênteses em torno do callback unário, porque eu posso!
Literais de objetos em esteróides
Os literais de objeto no ES6 têm alguns aprimoramentos de desempenho sérios. Eles podem fazer todos os tipos de coisas nos dias de hoje!
Nomes de propriedades dinâmicas
Eu realmente não percebi até escrever este artigo que desde o CoffeeScript 1.9.1 agora podemos fazer isso:
dynamicProperty = 'foo' obj = {"#{dynamicProperty}": 'bar'}
O que é muito menos doloroso do que algo assim que era necessário antes:
dynamicProperty = 'foo' obj = {} obj[dynamicProperty] = 'bar'
O ES6 tem uma sintaxe alternativa que eu acho muito legal, mas não é uma grande vitória:
let dynamicProperty = 'foo' let obj = { [dynamicProperty]: 'bar' }
Atalhos divertidos
Na verdade, isso foi tirado do CoffeeScript, mas era algo que eu ignorava até agora. É muito útil de qualquer maneira:
let foo = 'foo' let bar = 'bar' let obj = { foo, bar }
Agora o conteúdo de obj é { foo: 'foo', bar: 'bar' }
. Isso é super útil e vale a pena lembrar.
Tudo o que uma classe pode fazer eu também posso!
Literais de objetos agora podem fazer praticamente tudo que uma classe pode fazer, até mesmo algo como:
let obj = { _foo: 'foo', get foo() { return this._foo }, set foo(str) { this._foo = str }, isFoo() { return this.foo === 'foo' } }
Não tenho certeza por que você gostaria de começar a fazer isso, mas ei, agora você pode! Você não pode definir métodos estáticos, é claro... pois isso não faria sentido algum.
For-loops para confundir você
A sintaxe for-loop do ES6 apresentará alguns bugs de memória muscular para CoffeeScripters experientes, já que o for-of do ES6 se traduz em for-in do CoffeeSCript e vice-versa.
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 mudar para ES6?
Atualmente estou trabalhando em um projeto Node.js bastante grande usando 100% CoffeeScript, e ainda estou muito feliz e super produtivo com ele. Eu diria que a única coisa que eu realmente tenho inveja no ES6 são os getters e setters.
Também ainda é um pouco doloroso usar o ES6 na prática hoje. Se você puder usar a versão mais recente do Node.js, obterá quase todos os recursos do ES6, mas para versões mais antigas e no navegador as coisas ainda são menos otimistas. Sim, você pode usar o Babel, mas é claro que isso significa integrar o Babel à sua pilha.
Dito isto, posso ver o ES6 nos próximos um ou dois anos ganhando muito terreno, e espero ver coisas ainda maiores no ES7.
O que me deixa realmente satisfeito com o ES6 é que o “JavaScript simples e antigo” é quase tão amigável e poderoso pronto para uso quanto o CoffeeScript. Isso é ótimo para mim como freelancer – no passado eu costumava ser um pouco avesso a trabalhar em projetos JavaScript – mas com o ES6 tudo parece um pouco mais brilhante.