¿Qué hay de nuevo en ES6? Perspectiva de una conversión de CoffeeScript

Publicado: 2022-03-11

Soy fan de CoffeeScript desde hace más de dos años. Creo que soy más productivo escribiendo CoffeeScript, cometo menos errores tontos y, con la compatibilidad con los mapas de origen, la depuración del código CoffeeScript es completamente indolora.

¿Qué hay de nuevo en ES6? Perspectiva de una conversión de CoffeeScript

Recientemente he estado jugando con ES6 usando Babel y, en general, soy un fanático. En este artículo, recopilaré mis hallazgos sobre ES6 desde la perspectiva de un conversor de CoffeeScript, miraré las cosas que amo y veré qué cosas aún me faltan.

Indentación sintácticamente significativa: ¿una bendición o una perdición?

Siempre he tenido una pequeña relación de amor/odio con la sangría sintácticamente significativa de CoffeeScript. Cuando todo va bien, obtienes el "¡Mira mamá, sin manos!" fanfarronear de usar una sintaxis súper minimalista. Pero cuando las cosas salen mal y la sangría incorrecta causa errores reales (créanme, sucede), no parece que los beneficios valgan la pena.

 if iWriteCoffeeScript if iAmNotCareful badThings = 'happen'

Por lo general, estos errores se producen cuando tiene un bloque de código con algunas declaraciones anidadas y accidentalmente sangra incorrectamente una declaración. Sí, generalmente es causado por ojos cansados, pero en mi opinión, es mucho más probable que suceda en CoffeeScript.

Cuando comencé a escribir ES6, no estaba muy seguro de cuál sería mi reacción instintiva. Resultó que en realidad se sintió muy bien comenzar a usar llaves nuevamente. Usar un editor moderno también ayuda, ya que generalmente solo tiene que abrir la llave y su editor tiene la amabilidad de cerrarla por usted. Se sintió un poco como regresar a un universo tranquilo y claro después de la magia vudú de CoffeeScript.

 if (iWriteES6) { if (iWriteNestedStatements) { let badThings = 'are less likely to happen' } }

Por supuesto insisto en tirar los puntos y comas. Si no los necesitamos, digo tirarlos. Los encuentro feos y es escribir extra.

Apoyo de clase

El soporte de clase en ES6 es fantástico, y si se muda de CoffeeScript, se sentirá como en casa. Comparemos la sintaxis entre los dos con un ejemplo simple:

Clase 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` } }

Clase 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"

Primeras impresiones

Lo primero que puede notar es que ES6 sigue siendo mucho más detallado que CoffeeScript. Un atajo que es muy útil en CoffeeScript es el soporte para la asignación automática de variables de instancia en el constructor:

 constructor: (@numberOfLegs) ->

Esto es equivalente a lo siguiente en ES6:

 constructor(numberOfLegs) { this.numberOfLegs = numberOfLegs }

Por supuesto, ese no es realmente el fin del mundo y, en todo caso, esta sintaxis más explícita es más fácil de leer. Otra pequeña cosa que falta es que no tenemos un atajo @ para this , que siempre se sintió bien viniendo de un fondo Ruby, pero nuevamente no es un gran factor decisivo. En general, aquí nos sentimos como en casa y, de hecho, prefiero la sintaxis de ES6 para definir métodos.

¡Qué Súper!

Hay un pequeño problema con la forma en que se maneja super en ES6. CoffeeScript maneja super a la manera de Ruby ("envíe un mensaje al método de superclase del mismo nombre"), pero la única vez que puede usar un super "desnudo" en ES6 es en el constructor. ES6 sigue un enfoque similar a Java más convencional donde super es una referencia a la instancia de la superclase.

Voy a volver

En CoffeeScript podemos usar retornos implícitos para construir código hermoso como el siguiente:

 foo = -> if Math.random() > 0.5 if Math.random() > 0.5 if Math.random() > 0.5 "foo"

Aquí tiene una posibilidad muy pequeña de recuperar "foo" cuando llama a foo() . ES6 no tiene nada de esto y nos obliga a regresar usando la palabra clave de return :

 const foo = () => { if (Math.random() > 0.5 ) { if (Math.random() > 0.5 ) { if (Math.random() > 0.5 ) { return "foo" } } } }

Como podemos ver en el ejemplo anterior, esto es generalmente algo bueno, ¡pero todavía me olvido de agregarlo y obtengo resultados inesperados e indefinidos por todas partes! Hay una excepción con la que me he encontrado, con respecto a las funciones de flecha, donde obtienes un retorno implícito para una línea como esta:

 array.map( x => x * 10 )

Esto es algo útil, pero puede ser un poco confuso, ya que necesita el return si agrega las llaves:

 array.map( x => { return x * 10 })

Sin embargo, todavía tiene sentido. El razonamiento es que si ha agregado las llaves es porque desea usar varias líneas, y si tiene varias líneas, tiene sentido dejar en claro lo que está devolviendo.

Bonos de sintaxis de clase ES6

Hemos visto que CoffeeScript tiene algunos trucos sintácticos bajo la manga cuando se trata de definir clases, pero ES6 también tiene algunos trucos propios.

Getters y Setters

ES6 tiene un potente soporte para la encapsulación a través de getters y setters, como se ilustra en el siguiente ejemplo:

 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 } }

Gracias al captador, si accedemos a bananaStore.bananas solo devolverá plátanos maduros. Esto es realmente genial, ya que en CoffeeScript necesitaríamos implementarlo a través de un método getter como bananaStore.getBananas() . Esto no se siente del todo natural cuando en JavaScript estamos acostumbrados a acceder a las propiedades directamente. También hace que el desarrollo sea confuso porque necesitamos pensar para cada propiedad cómo debemos acceder a ella: ¿es .bananas o getBananas() ?

El setter es igualmente útil ya que podemos limpiar el conjunto de datos o incluso generar una excepción cuando se configuran datos no válidos, como se muestra en el ejemplo de juguete anterior. Esto será muy familiar para cualquier persona con experiencia en Ruby.

Personalmente, no creo que esto signifique que debas volverte loco usando getters y setters para todo. Si puede ganar algo al encapsular las variables de su instancia a través de getters y setters (como en el ejemplo anterior), continúe y hágalo. Pero imagine que solo tiene una bandera booleana como isEditable . Si no perdería nada al exponer directamente la propiedad isEditable , diría que ese es el mejor enfoque, ya que es el más simple.

Métodos estáticos

ES6 tiene soporte para métodos estáticos, lo que nos permite hacer este truco travieso:

 class Foo { static new() { return new this } }

Ahora, si odias escribir new Foo() , ahora puedes escribir Foo.new() . Los puristas se quejarán porque new es una palabra reservada, pero después de una prueba muy rápida, parece funcionar bien en Node.js y con los navegadores modernos. ¡Pero probablemente no quieras usar esto en producción!

Desafortunadamente, debido a que no hay soporte para propiedades estáticas en ES6, si desea definir constantes de clase y acceder a ellas en su método estático, debe hacer algo como esto, que es un poco complicado:

 class Foo { static imgPath() { return `${this.ROOT_PATH}/img` } } Foo.ROOT_PATH = '/foo'

Hay una propuesta de ES7 para implementar propiedades declarativas de instancia y clase, de modo que podamos hacer algo como esto, lo que sería bueno:

 class Foo { static ROOT_PATH = '/foo' static imgPath() { return `${this.ROOT_PATH}/img` } }

Esto ya se puede hacer con bastante elegancia en CoffeeScript:

 class Foo @ROOT_PATH: '/foo' @imgPath: -> @ROOT_PATH

Interpolación de cadenas

¡Ha llegado el momento en que finalmente podemos hacer la interpolación de cadenas en Javascript!

 `I am so happy that in the year ${new Date().getFullYear()} we can interpolate strings`

Viniendo de CoffeeScript/Ruby, esta sintaxis se siente un poco asquerosa. Tal vez debido a mi experiencia en Ruby, donde se usa un acento grave para ejecutar comandos del sistema, se siente bastante mal al principio. También usar el signo de dólar se siente como en los años ochenta, pero tal vez solo soy yo.

Supongo que debido a problemas de compatibilidad con versiones anteriores, simplemente no fue posible implementar la interpolación de cadenas de estilo CoffeeScript. "What a #{expletive} shame" .

Funciones de flecha

Las funciones de flecha se dan por sentadas en CoffeeScripter. ES6 prácticamente implementó la sintaxis de flecha gruesa de CoffeeScript. Entonces obtenemos una buena sintaxis corta, y obtenemos el alcance léxico de this , por lo que no tenemos que pasar por aros como este cuando usamos ES5:

 var that = this doSomethingAsync().then( function(res) { that.foo(res) })

El equivalente en ES6 es:

 doSomethingAsync().then( res => { this.foo(res) })

Observe cómo omití el paréntesis alrededor de la devolución de llamada unaria, ¡porque puedo!

Literales de objeto en esteroides

Los objetos literales en ES6 tienen algunas mejoras de rendimiento importantes. ¡Pueden hacer todo tipo de cosas en estos días!

Nombres de propiedades dinámicas

En realidad, no me di cuenta hasta que escribí este artículo que desde CoffeeScript 1.9.1 ahora podemos hacer esto:

 dynamicProperty = 'foo' obj = {"#{dynamicProperty}": 'bar'}

Lo cual es mucho menos doloroso que algo como esto que era necesario antes:

 dynamicProperty = 'foo' obj = {} obj[dynamicProperty] = 'bar'

ES6 tiene una sintaxis alternativa que creo que es bastante buena, pero no es una gran victoria:

 let dynamicProperty = 'foo' let obj = { [dynamicProperty]: 'bar' }

Atajos funky

Esto en realidad está tomado de CoffeeScript, pero era algo que desconocía hasta ahora. Es muy útil de todos modos:

 let foo = 'foo' let bar = 'bar' let obj = { foo, bar }

Ahora el contenido de obj es { foo: 'foo', bar: 'bar' } . Eso es muy útil y vale la pena recordarlo.

¡Todo lo que una clase puede hacer, yo también lo puedo hacer!

Los objetos literales ahora pueden hacer casi todo lo que una clase puede hacer, incluso algo como:

 let obj = { _foo: 'foo', get foo() { return this._foo }, set foo(str) { this._foo = str }, isFoo() { return this.foo === 'foo' } }

No estoy muy seguro de por qué querrías comenzar a hacer eso, pero oye, ¡ahora puedes! Por supuesto, no puede definir métodos estáticos... ya que eso no tendría ningún sentido.

For-bucles para confundirte

La sintaxis del bucle for de ES6 va a introducir algunos errores de memoria muscular agradables para los usuarios experimentados de CoffeeScript, ya que el for-of de ES6 se traduce en for-in de CoffeeSCript, y viceversa.

ES6

 for (let i of [1, 2, 3]) { console.log(i) } // 1 // 2 // 3

CaféScript

 for i of [1, 2, 3] console.log(i) # 0 # 1 # 2

¿Debería cambiarme a ES6?

Actualmente he estado trabajando en un proyecto bastante grande de Node.js usando 100% CoffeeScript, y todavía estoy muy feliz y superproductivo con él. Diría que lo único de lo que realmente estoy celoso en ES6 son los getters y setters.

Además, todavía es un poco doloroso usar ES6 en la práctica hoy. Si puede usar la versión más reciente de Node.js, obtiene casi todas las funciones de ES6, pero para versiones anteriores y en el navegador, las cosas aún son menos optimistas. Sí, puede usar Babel, pero, por supuesto, eso significa integrar Babel en su pila.

Habiendo dicho eso, puedo ver que ES6 en los próximos uno o dos años ganará mucho terreno, y espero ver cosas aún mejores en ES7.

Lo que realmente me complace de ES6 es que el "JavaScript simple y antiguo" es casi tan amigable y poderoso listo para usar como CoffeeScript. Esto es genial para mí como trabajador independiente; en el pasado, solía ser un poco reacio a trabajar en proyectos de JavaScript, pero con ES6 todo parece un poco más brillante.