What's New in ES6? Perspective of a CoffeeScript Convert
CoffeeScript allows developers to make the most out of JavaScript-based platforms without having to jump through its awkward language hoops. However, with the introduction of ES6 features into major JavaScript engines, plain JavaScript is now nearly as friendly and powerful out-of-the-box as CoffeeScript.
In this article, Toptal engineer William Coates shares his findings on ES6 from the perspective of a CoffeeScript convert.
CoffeeScript allows developers to make the most out of JavaScript-based platforms without having to jump through its awkward language hoops. However, with the introduction of ES6 features into major JavaScript engines, plain JavaScript is now nearly as friendly and powerful out-of-the-box as CoffeeScript.
In this article, Toptal engineer William Coates shares his findings on ES6 from the perspective of a CoffeeScript convert.
A full-stack developer and entrepreneur with 15+ years of experience, William (MSc) loves to keep up-to-date with the latest web tech.
Expertise
I’ve been a CoffeeScript fan for over two years now. I find I’m more productive writing CoffeeScript, make less silly mistakes, and with support for source maps debugging CoffeeScript code is completely painless.
Recently I’ve been playing with ES6 using Babel, and overall I’m a fan. In this article, I will compile my findings on ES6 from the perspective of a CoffeeScript convert, look at the things I love and see what things I’m still missing.
Syntactically Significant Indentation: a Boon or a Bane?
I’ve always had a bit of a love/hate relationship with CoffeeScript’s syntactically significant indentation. When everything goes fine you get the “Look ma, no hands!” bragging rights of using such a super-minimalistic syntax. But when things go wrong and incorrect indentation causes real bugs (trust me, it happens), it doesn’t feel like the benefits are worthwhile.
if iWriteCoffeeScript
if iAmNotCareful
badThings = 'happen'
Typically these bugs are caused when you have a block of code with a few nested statements, and you accidentally indent one statement incorrectly. Yes it’s usually caused by tired eyes, but it’s much more likely to happen in CoffeeScript in my opinion.
When I started writing ES6 I wasn’t quite sure what my gut reaction would be. It turns out it actually felt really nice to start using curly braces again. Using a modern editor also helps, as generally you only have to open the brace and your editor is kind enough to close it for you. It felt a bit like returning to a calm, clear universe after the voodoo magic of CoffeeScript.
if (iWriteES6) {
if (iWriteNestedStatements) {
let badThings = 'are less likely to happen'
}
}
Of course I insist on throwing out the semicolons. If we don’t need them, I say throw them out. I find them ugly and it’s extra typing.
Class Support
The class support in ES6 is fantastic, and if you’re moving from CoffeeScript you will feel right at home. Let’s compare the syntax between the two with a simple example:
ES6 Class
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`
}
}
CoffeeScript Class
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"
First Impressions
The first thing that you may notice is that ES6 is still a lot more verbose than CoffeeScript. One shortcut that is very handy in CoffeeScript is the support for automatic assignment of instance variables in the constructor:
constructor: (@numberOfLegs) ->
This is equivalent to the following in ES6:
constructor(numberOfLegs) {
this.numberOfLegs = numberOfLegs
}
Of course that’s not really the end of the world, and if anything this more explicit syntax is easier to read.
Another small thing missing is that we have no @
shortcut for this
, which always felt nice coming from a Ruby background, but again isn’t a massive deal-breaker. In general we feel pretty much at home here, and I actually prefer the ES6 syntax for defining methods.
How Super!
There is a small gotcha with the way super
is handled in ES6. CoffeeScript handles super
in the Ruby way (“please send a message to the superclass method of the same name”), but the only time you can use a “naked” super in ES6 is in the constructor. ES6 follows a more conventional Java-like approach where super
is a reference to the superclass instance.
I Will Return
In CoffeeScript we can use implicit returns to build beautiful code like the following:
foo = ->
if Math.random() > 0.5
if Math.random() > 0.5
if Math.random() > 0.5
"foo"
Here you have a very small chance of getting “foo” back when you call foo()
. ES6 is having none of this and forces us to return using the return
keyword:
const foo = () => {
if (Math.random() > 0.5 ) {
if (Math.random() > 0.5 ) {
if (Math.random() > 0.5 ) {
return "foo"
}
}
}
}
As we can see in the above example, this is generally a good thing, but I still find myself forgetting to add it and getting unexpected undefined returns all over the place! There is one exception I’ve come across, concerning Arrow Functions, where you get an implicit return for one liners like this:
array.map( x => x * 10 )
This is kind of handy but can be slightly confusing as you need the return
if you add the curly braces:
array.map( x => {
return x * 10
})
However, it still makes sense. The reasoning being that if you have added the curly braces it’s because you want to use multiple lines, and if you have multiple lines it makes sense to be clear what you are returning.
ES6 Class Syntax Bonuses
We’ve seen that CoffeeScript has a few syntactical tricks up its sleeve when it comes to defining classes, but ES6 also has a few tricks of its own.
Getters and Setters
ES6 has powerful support for encapsulation via getters and setters, as illustrated in the following example:
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
}
}
Thanks to the getter, if we access bananaStore.bananas
it will only return ripe bananas. This is really great as in CoffeeScript we would need to implement this via a getter method like bananaStore.getBananas()
. This doesn’t feel at all natural when in JavaScript we are used to accessing properties directly. It also makes development confusing because we need to think for each property how we should access it - is it .bananas
or getBananas()
?
The setter is equally useful as we can clean up the data set or even throw an exception when invalid data is set, as shown in the toy example above. This will be very familiar to anyone from a Ruby background.
I personally don’t think this means you should go crazy using getters and setters for everything. If you can gain something by encapsulating your instance variables via getters and setters (like in the example above) then go ahead and do so. But imagine you just have a boolean flag such as isEditable
. If you would not lose anything by directly exposing the isEditable
property, I would argue that’s the best approach, as it’s the simplest.
Static methods
ES6 has support for static methods, which lets us do this naughty trick:
class Foo {
static new() {
return new this
}
}
Now if you hate writing new Foo()
you can now write Foo.new()
. Purists will grumble since new
is a reserved word, but after a very quick test it seems to work in Node.js and with modern browsers just fine. But you probably don’t want to use this in production!
Unfortunately because there is no support for static properties in ES6, if you want to define class constants and access them in your static method you’d have to do something like this, which is a bit hackish:
class Foo {
static imgPath() {
return `${this.ROOT_PATH}/img`
}
}
Foo.ROOT_PATH = '/foo'
There is an ES7 Proposal to implement declarative instance and class properties, so that we can do something like this, which would be nice:
class Foo {
static ROOT_PATH = '/foo'
static imgPath() {
return `${this.ROOT_PATH}/img`
}
}
This can already be done quite elegantly in CoffeeScript:
class Foo
@ROOT_PATH: '/foo'
@imgPath: ->
@ROOT_PATH
String Interpolation
The time has come when we can finally do string interpolation in Javascript!
`I am so happy that in the year ${new Date().getFullYear()} we can interpolate strings`
Coming from CoffeeScript/Ruby, this syntax feels a bit yuk. Perhaps because of my Ruby background where a backtick is used to execute system commands, it feels pretty wrong at first. Also using the dollar sign feels kind of eighties – but maybe that’s just me.
I suppose due to backwards compatibility concerns it just wasn’t possible to implement CoffeeScript style string interpolation. "What a #{expletive} shame"
.
Arrow Functions
Arrow functions are taken for granted in CoffeeScripter. ES6 pretty much implemented the fat arrow syntax of CoffeeScript. So we get a nice short syntax, and we get the lexically scoped this
, so we don’t have to jump through hoops like this when using ES5:
var that = this
doSomethingAsync().then( function(res) {
that.foo(res)
})
The equivalent in ES6 is:
doSomethingAsync().then( res => {
this.foo(res)
})
Notice how I left out the parenthesis around the unary callback, because I can!
Object Literals on Steroids
Object literals in ES6 have some serious performance enhancements. They can do all kinds of things these days!
Dynamic Property Names
I didn’t actually realise until writing this article that since CoffeeScript 1.9.1 we can now do this:
dynamicProperty = 'foo'
obj = {"#{dynamicProperty}": 'bar'}
Which is much less of a pain than something like this which was necessary before:
dynamicProperty = 'foo'
obj = {}
obj[dynamicProperty] = 'bar'
ES6 has an alternative syntax which I think is pretty nice, but not a huge win:
let dynamicProperty = 'foo'
let obj = { [dynamicProperty]: 'bar' }
Funky Shortcuts
This is actually taken from CoffeeScript, but it was something I was ignorant of until now. It’s very useful anyhow:
let foo = 'foo'
let bar = 'bar'
let obj = { foo, bar }
Now the contents of obj is { foo: 'foo', bar: 'bar' }
. That’s super useful, and worth remembering.
Everything a Class Can Do I Can Do Too!
Object literals can now do pretty much everything a class can do, even something like:
let obj = {
_foo: 'foo',
get foo() {
return this._foo
},
set foo(str) {
this._foo = str
},
isFoo() {
return this.foo === 'foo'
}
}
Not quite sure why you would want to start doing that but hey, now you can! You can’t define static methods of course… as that would make no sense at all.
For-loops to Confuse You
The ES6 for-loop syntax is going to introduce some nice muscle memory bugs for experienced CoffeeScripters, as the for-of of ES6 translates to for-in of CoffeeSCript, and 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
Should I Switch to ES6?
I’ve currently been working on a fairly large Node.js project using 100% CoffeeScript, and I’m still very happy and super productive with it. I would say that the only thing I am really jealous of in ES6 are the getters and setters.
Also it is still slightly painful using ES6 in practice today. If you are able to use the very latest Node.js version, then you get nearly all ES6 features, but for older versions and in the browser things are still less rosy. Yes, you can use Babel, but of course that means integrating Babel into your stack.
Having said that I can see ES6 over the next year or two gaining a lot of ground, and I hope to see even greater things in ES7.
What I’m really pleased about with ES6 is that “plain old JavaScript” is nearly as friendly and powerful out-of-the-box as CoffeeScript. This is great for me as a freelancer – in the past I used to be a little bit averse to working on JavaScript projects – but with ES6 everything just seems that little bit shinier.
Lisbon, Portugal
Member since October 23, 2015
About the author
A full-stack developer and entrepreneur with 15+ years of experience, William (MSc) loves to keep up-to-date with the latest web tech.