The Best of ES2015
Hi, there, fellow JavaScript-er! You are a crazy person, one of many. And the numbers are really only increasing. JavaScript is dominating all sorts of platforms nowadays. It is available in browsers but also on servers, robots, wearables, phones, and hopefully soon in our kernels.
As the popularity of the language increases, the need for the language to evolve is also increasing. As a result, the ECMAScript community has reevaluated the design process of the language from hereon forth:
The plan is to release a new version of ECMAScript every year, with whatever features are ready at that time. That means that from now on, ECMAScript versions will be relatively small upgrades.
[Source: http://exploringjs.com/es6/ch_about-es6.html]
Considering the release gap between ES5 and ES6, this news is quite incredible. It’s also quite incredible because it paves the way for many of the increasingly necessary JavaScript features to become part of the language increase of becoming part of a library that is considered a boilerplate.
So here’s a rundown of ES2015 or at least, what I feel are the most important features to know.
Changes in Scope & Immutability
One of the big differences in JavaScript and most other programming languages that tends to stand out is how in JavaScript all variables are defined with function-scope rather than block-scope. This becomes especially peculiar when your realize that JavaScript best practices for writing for-loops is to define your counter variables at the top of the function:
(function () {
'use strict'; var array = [1, 2, 3], i; for (i = 0; i < arrray.length; i += 1) {
console.log(array[i]);
} // this line will print "3" since i still
// exists outside of the loop
console.log(i);
}());
But fear no more! Block-level variables are now a thing using the ‘let’ statement:
(function () {
'use strict'; var array = [1, 2, 3]; for (let i = 0; i < array.length; i += 1) {
console.log(array[i]);
} console.log(i); // ReferenceError: i is undefined
}());
This also causes the for-loop to create a separate block-level scope on every iteration. Due to this, you don’t have to create wrapper functions to create functions inside of for-loops anymore. For instance, this is the ES5 way of creating functions inside of for-loops:
var array = [1, 2, 3], i;
var createFn = function (index) {
return function () {
return array[index] * 2;
};
};for (i = 0; i < array.length; i += 1) {
array[i] = createFn(i);
}
But now with block-scope variables, you can just do this:
var array = [1, 2, 3];for (let i = 0; i < array.length; i += 1) {
array[i] = function () { return array[i] * 2; };
}
Furthermore, ES2015 also introduces the concept of immutable variables. This is for all those times that you are trying to define variables that you don’t want any other programs modifying (or accidentally being modified at runtime). The way to define a variable as immutable is to use the ‘const’ variable (i.e. constant):
const myVariable = 'hello, world';myVariable = 'testing'; // TypeError: Assignment to constant variable.
Native Promises
Promises have become the new normal for implementing asynchronous JS because they solve the problems of async callbacks:
- Callback hell goes away.
- They come with a standardized API (so we can link async actions across many libraries together).
But one of the big reasons that it has been difficult for promises to be used universally in JS is because there has been a lack of native support. We have had to use polyfills and libraries to get promises to work. With ES2015, promises are now native:
var doSomethingAsync = function () {
return new Promise(function (resolve, reject) {
// do something asynchronous then call resolve with the results
// or call reject with an error
});
};
All promise objects have an API that is quite similar to the control flow of a try/catch statement. To attach a success callback to a promise, you use the ‘.then()’ method — this method also accepts a second parameter which would be bound as the error callback. You can attach specifically to promise rejection by using the ‘.catch()’ method or run a callback either way by using the ‘.finally()’ method:
promise
.then(function (result) {
console.log(result);
})
.catch(function (error) {
console.error('Error: ' + error);
})
.finally(function () {
console.log('I\'m done.');
});
Promises also come with several class-level methods such as Promise.all() that allows for you to concatenate many promises into one:
var bigPromise = Promise.all([
new Promise(function (resolve, reject) {
resolve(Math.random() * 10);
}),
new Promise(function (resolve, reject) {
resolve(Math.random() * 10);
})
]);bigPromise.then(/* add your success callback here */);
Arrow Functions
Let’s start with arrow functions! These are probably your first clue when reading some JavaScript about whether or not you’re reading post-ES5 code. But what the hell are they?
Many beginners and those that have not yet dove into ES2015 yet think of them as shorthands for writing functions. Although this is one use case for them, it is not the reason that they exist.
Arrow function syntax is a way for creating functions that are strictly bound to the context within which they are created. But why? Well because this actually becomes quite an important thing when you’re creating helper functions within other functions. Typically, you won’t get into this until you begin to write a lot of JavaScript that is centred around contexts (i.e. when creating complex classes or back when I still wrote jQuery).
The ES5 way of doing arrow functions is this:
var ctxBoundFn = function () {
// your logic goes here
}.bind(this);
Destructuring
This ES2015 feature is actually very cool and useful. Destructuring essentially allows your code to destructure a JSON object or an array into individual variables.
The best part of this feature is the syntax. Let’s say that I have an object describing a person but I need to extract the person’s first name and age specifically for a particular function. I can write the parameter of the function like so:
var extractAgeAndName = function ({ name: name, age: age }) {
// i can now use 'name' and 'age' as variables within this
// function's scope
};
I can also do this outside of parameters, like during an assignment:
let [a, b, c] = [1, 2, 3];a === 1; // true
b === 2; // true
c === 3; // true
Iterables & Iterators
This is where ES2015 gets a bit complicated. The documentation pertaining to iterables and iterators defines an object as iterable if it implements the @@iterator method (which means that you can create an iterator out of it). An iterator is then defined as an object with a ‘next()’ method that must return JSON object with two properties:
- done: a boolean describing whether iteration is complete.
- value: a variant containing the value at the current iteration.
For instance, arrays are now iterables. This means that when you create an iterator from them, you can do this:
var array = [1, 2, 3];
var iterator = array[Symbol.iterator](); // yes, it's weird
var item = iterator.next();// this loop should print out:
// 1
// 2
// 3while (!item.done) {
console.log(item.value);
item = iterator.next();
}
But obviously, this doesn’t seem exciting. That is until you realize that there’s a new sort of for loop that has been introduced alongside the iterable protocol and that is the for…of loop.
The for…of tries to create an iterator out of the object you give it and allows you to simply use the values that the iterator spits out:
// this will also print:
// 1
// 2
// 3for (let value of [1, 2, 3]) {
console.log(value);
}
This is incredibly useful for when you just want to loop through array values but don’t care about the index. However, I feel like the iterator should have been defined more like this (you can use this in your code if you wish):
Array.prototype[Symbol.iterator] = function* () {
for (let i = 0; i < this.length; i += 1) {
yield { index: i, value: this[i] };
}
};
[Source: https://gist.github.com/karimsa/380cd70338dc17eecba26c48f9b9d579]
Once you add this code, the array iterators will return both indices and values and you can choose what you need via destructuring:
for ( let { value: v, index: i } of [1, 2, 3] ) {
console.log(v + ' is at ' + i);
}
Template Strings
Another long overdue feature. How many times do you find yourselves writing JavaScript in which you are concatenating strings? All the time.
Template strings try to make this easier on your code by allowing you to write your strings with back-ticks instead of quotes and your strings will gain two special characteristics:
- They can be multi-line. The interpreter will consider everything in between the back-ticks to be a string and will not complain if you decide to use several lines.
- You can interpolate all you want.
The second part is the fancy part. Within these template literals, you can use the dollar sign to tell JavaScript to replace an expression with its string equivalent within the literal and then enclose your expression in curly braces:
var name = 'wo';
var myString = `Hello, ${name + 'rld'}`;
That’s all, folks.
The feature set of ES2015 is too broad to discuss in a single blog post, but I have tried to include the parts that I think are the most important to know.
For a full list of features, see this awesome website: es6-features.org.
Have fun playing with ES2015 and get excited for ES2016 (which brings even crazier features into JS)!
Click here to see the full publication where this article is included.
Help me keep things free by using flattr.