Just want examples of ES6 in use? I’ve put four ES6 projects on Github.
ES6 has been getting a lot of press lately. And with all of the benefits that the new syntax and built-ins bring, it damn well should. But given the breadth of the changes, how is a forward-thinking developer to know where to start? What is worth practicing now, and what is best left for a rainy day?
Well, after spending the last few weeks writing maxim and memamug with ES6, and translating numbat-ui from coffeescript, there are a few parts which have stood out to me.
Destructuring
They say the simpler things in life are sometimes the best, and so it goes with ES6. In fact, the feature which has left the biggest impression on me isn’t Promises, isn’t Generators, and isn’t Classes – it is Destructuring.
Why? Have an example:
ES6
function initialize({controls = {}, models = {}, reducers = {}, actors = []}) {
// ...
}
ES5
function initialize(options) {
var controls = options.controls || {};
var models = options.models || {};
var reducers = options.reducers || {};
var actors = options.actors || {};
}
ES6 code taken from index.js in Maxim
As seen here, destructuring works wonders for function arguments. But it also works great with:
for .. of loops
Say you’ve got an Array of pairs as you’d get from the Object.entries method proposed for ES7, and you want to loop through them. for .. of
with destructuring makes this (almost) fun:
ES6
for (let [name, builder] of Object.entries(models)) {
// do something
}
ES5
var entries = Object.entries(models);
for (var i = 0; i != entries.length; i++) {
var entry = entries[i];
var name = entry[0];
var builder = entry[1];
}
ES6 code taken from index.js in Maxim
This is nice, but why limit ourselves to iterating through arrays? The beauty of ES6 is that Arrays aren’t special anymore – you can use for .. of
to iterate through anything which defines an Iterator. Like:
Map
Maps are kind of like objects – they map keys to values. The two major differences are:
- Object can only have Strings and Symbols as keys (Maps can have almost anything)
- Maps are iterable
In fact, a Map’s iterator produces 2-element arrays each with a key/value pair – like the above example. This allows you to do neat things like this:
ES6
this.propFns = this.propFns || new Map
for (const event of events) {
const name = 'on' + capitalize(event)
if (!this.propFns.has(name)) {
this.propFns.set(name, [])
}
this.propFns.get(name).push(fn)
}
// later...
for (let [name, fns] of this.propFns || []) {
// ...
}
ES5
this.propFns = this.propFns || {}
events.forEach(function(event) {
const name = 'on' + capitalize(event)
if (!this.propFns[name]) {
this.propFns[name] = []
}
this.propFns[name].push(fn)
})
// later...
for (var name in this.propFns) {
if (this.propFns.hasOwnProperty(name)) {
var fns = this.propFns[name];
// ..
}
}
Code inspired by base.js in react-base
While there isn’t too much difference in setting the two examples up, actually using a Map of keys/pairs feels much more elegant than the equivalent example using Objects. Map also has Map.prototype.values
and Map.prototype.keys
available, in case you don’t need them both keys and values.
Have you noticed that I’ve been using some new syntax without introducing it? Drum roll please. Let’s do so now!
let / const
While everything I’ve shown you until now makes JavaScript feel noticeably nicer, let
makes it feel a lot less icky.
Why? Code speaks louder than words:
ES5
// Find the index of the first ripple which has started but isn't yet ending
for (var i = 0; i != ripples.length; i++) {
if (ripples[i].started && !ripples[i].ending) {
break;
}
}
// Do something unrelated which we probably added at a later date
for (var j = 0; j != arr.length; j++) {
for (var i = 0; i != arr[j].length; i++) {
// do something...
}
}
// Oops.
var endingRipple = ripples[i];
Example inspired by TouchRipple.jsx in numbat-ui
So the above code doesn’t work. I’m sure you know what the problem is, but have a think about it anyway. Once you’ve got it, check by touching or clicking the box below:
The
i
variables which holds the result of our first loop is modified in the second loop, before we try and use it to access the ripple object we were looking for.
Why? Variables defined with var
aren’t actually defined where the var
appears – they’re defined at the top of the function they appear in (yes – the function – not the loop).
You may think you won’t ever make this mistake. But if you’re using ES5, you will.
However, using let
in place of var
in the above code would have actually produced the result we expect, as variables defined with let
only come into existence in the block of code they’re defined in. I.e. they’re block-scoped variables.
const
is just like let
, except you can’t re-assign it. Be careful though, you can still modify any objects/arrays which it points to:
const lyrics = 'badger';
lyrics = 'mushroom';
// ERROR!
const lyrics = ['badger', 'badger', 'badger', 'badger', 'badger', 'badger', 'badger', 'badger', 'badger', 'badger', 'badger', 'badger'];
lyrics.push('mushroom').
// OK
Of course we’d never actually write any code like the above example in ES6, because of the new:
ES6 Array methods
In particular, Array.prototype.findIndex will give you the index of the first item from an array which matches the given predicate function.
This means we can rewrite the first loop in the above example like this:
const i = ripples.findIndex(function(r) { return r.started && !r.ending; });
But why return the index when you can just return the object you’re looking for straight up with Array.prototype.find?
const endingRipple = ripples.find(function(r) { return r.started && !r.ending; });
Other new array methods include:
Of course, it is all well and good knowing these methods exist – but unless you’re a memorisation pro, it’ll be a while before you can use them without googling. That is, unless you have my print-optimised ES6 cheatsheet! It describes each of the new array methods, and you’ll receive it immediately after subscribing to my newsletter. Convenient! But I digress.
Get the PDF cheatsheet!
Arrow Functions
See that one-liner function in the example above? That was a contrived example. In the real ES6 code I took the example from, instead of using the old function
syntax to define a function, I instead used the new =>
syntax.
const endingRipple = ripples.find(r => r.started && !r.ending);
Code is from TouchRipple.jsx in numbat-ui
Very pretty, right? Actually, very normal if you’ve been writing coffeescript for the past few years, but I digress.
Arrow functions can take on a number of forms. Here is a decidedly less pretty one:
ES6
const targetFactory = ({options, children}) => {
let zIndex = 1
if (this.props.disabled) zIndex = 0
else if (this.state.touched) zIndex = 2
const inner = this.props.targetFactory(
Object.assign(options, {className: this.c('container')}),
React.createElement(Paper, {zDepth}, children)
)
return React.createElement(Target, {on: this.setTouched, off: this.unsetTouched}, inner)
}
Code from RaisedButton.jsx in numbat-ui
See the this
inside the function? In a normal function, you’d have no fucking clue what this this
meant if you passed the function to someone else, unless you bound it with Function.prototype.bind
. With =>
, this
means the same thing inside the function as it does outside. Even if you pass it somewhere else. Even if you bind it to something else.
In short, function(args) { return ... }.bind(this)
is equivalent to args => ...
Except when it’s not:
- You need to use braces around the function contents if you use more than a single expression
- If you use braces, you still need a return statement
- You can’t emit the brackets around a single function argument if you use object destructuring on it
All these rules can be a little hard to remember. But don’t worry! It’s all in the cheatsheet at the bottom of the post. So let’s move on to:
Template Literals
Template literals (i.e. strings delimited by ``
backticks) let you:
- Interpolate JavaScript into your string using
${}
- Write multiple lines of string
- Do all sorts of other crazy things like “tagging” them
Honestly, I just use the interpolation. That’s pretty boring, so let’s throw ES7’s proposed fetch method into the example to spice it up a little:
const promise = fetch('/api/v1/contacts', {
method: 'post',
headers: {
'Authorization': `Token token=${identity.get('accessToken')}`,
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
})
Taken from ContactControl.js in memamug-client
See that promise
variable we assigned the result of fetch
to? It’s value is a built-in class new to ES6. It’s name, surprise surprise, is:
Promise
Promises are a way of making asynchronous code nicer. In the example above, the promise represents the result of the HTTP fetch. We can add callbacks to promises to be notified when the request completes (or fails valiantly) using their then(onSuccess, onFailure)
method.
Promises aren’t a small topic, so a full explanation wouldn’t fit here. But heres one I prepared earlier:
Introduction to ES6 Promises – The Four Functions You Need To Avoid Callback Hell
Now, moving right along to:
… (spread operator and rest parameters)
Known as splats in some other languages, these provide a convenient way to shove any unnamed arguments passed to your function into an array, or do the inverse by calling a function with arguments extracted from an array.
Again, you’ll probably find this easier to understand by looking at it:
c(...args) {
return (
classNames(...args)
.split(/\s+/)
.filter(name => name !== "")
.map(name => `{namespace}-${this.constructor.name}-${name}`)
.join(' ')
);
}
Taken from base.js in react-base
So you can pass any number of arguments to c
, and they’ll end up in the args
array. Similarly, classNames
will then receive these as separate arguments, not as a single array.
On a completely different note, see how we’re defining this function without the function keyword? This trick is actually part of the syntax for:
Classes
Yep, ES6 has classes. These could have an entire article written about them just by themselves. Maybe I’ll write one, one day. You’ll find out if you sign up to my newsletter. Just like you’ll find out when I write more about:
import & export
Actually, import
and export
are probably my favourite ES6 feature, but I’ll leave these for a later post. You won’t be using them directly in browsers anytime soon, but combined with a good build setup, you don’t have to use them directly in browsers. But why wait until I write about class
, import
and export
, when almost every file I’ve pulled examples from so far uses them?
Example ES6 code
It is all available at my github, with four projects I’d recommend:
- memamug-client (the source for memamug.com)
- maxim (to help structure your single page apps)
- react-base (provide common utilities to your react components)
- numbat-ui (material design component library for react)
Need a little help getting an environment where you can use ES6 up and running? Check out my article on getting started with webpack.
Cheatsheet
Phew, that was a lot of information. The key to remembering it all is to use it, but searching through this article for just the bit you want each time sure sounds like a lot of work.
Thats why I’ve created this spiffy print-optimised cheat sheet (see preview). It is perfect for printing out and hanging next to your monitor or on your toilet door, or to use in fashionable paper-mache clothing.
The high resolution PDF is available exclusively to my newsletter subscribers – sign up now and it’ll be immediately e-mailed to you for your reading pleasure, along with a handy-dandy JavaScript Promises cheatsheet!
I will send you useful articles, cheatsheets and code.
Read More
Of course, knowing ES6 isn’t much help if the browser doesn’t understand it. And that’s why you need Webpack/Babel:
- Using ES6 and ES7 in the Browser, with Babel 6 and Webpack
- Webpack Made Simple: Building ES6/LESS with Autorefresh
Minor comment, your link to “Symbols” actually points to the MDN resource for “Maps”
Oops, well caught. Thanks.
Beautiful Design and valuable Content!
Congratulations!!
“i.e. strings delimeted by…”
*delimited
Thanks!
The snippet in the arrow functions is now found in SelectRipple.jsx#L82:
https://github.com/jamesknelson/numbat-ui/blob/c8c68996c4b2300b7e3a50ff0c69c16bef564b05/src/components/SelectRipple/SelectRipple.jsx#L82
A trick when linking to GitHub code: you can get the permalink to the current master commit by pressing ‘Y’, which will replace the ‘master’ in the URL to the actual commit hash.
Great post!
Same on the RaisedButton.jsx: https://github.com/jamesknelson/numbat-ui/blob/6d5e2f8dc347c51f2c268fede0725906babdf9f7/src/components/RaisedButton/RaisedButton.jsx#L53
Where you write “You can’t emit the brackets”, I think you mean “omit”?