EDITING BOARD
RO
EN
×
▼ BROWSE ISSUES ▼
Issue 42

ECMAScript 6 - why and how to use it today

Adrian Ulici
Software Engineer @3Pillar Global
PROGRAMMING

Introduction

For those of you who haven't heard of ECMAScript, simply put, ECMAScript is the standard which defines the programming language which we popularly know as JavaScript. The previous edition of the standard, ECMAScript 5, was released at the end of 2009, with significant improvements to the language, but it was still lacking some important features. ECMAScript 6 (ES6), on the other hand, has finally introduced features developers were waiting for years. Actually, the official name is ECMAScript 2015, but it seems that in the community ES6 is still the version that most people recognize (at least for now).

I will discuss below some of the improvements which I personally like and think will help me most. Of course, depending on the type of the project you may find other features useful.

Some new major features

Arrow functions

Arrow functions are a new, shorter way to write anonymous functions. If you are a programmer with a preference for functional programming you will definitely love it. Here's a short example:

// es6 version
[1, 2, 3].map((item, index) => {
    return item * 2;
});
// es5 version
[1, 2, 3].map(function(item, index) {
    return item * 2;
});

As you can see, we dropped the "function" keyword, which already saves us some characters, but it gets even better. If the arrow function only needs one argument, you can also get rid of the opening and closing parentheses from the arguments list:

[1, 2, 3].map(item => {
    return item * 2;
});

That looks good, but we're not done! There's still an improvement we can do here: if the body of the arrow function is only a return statement, we can get rid of the brackets and of the "return" keyword also, and we get something like:

[1, 2, 3].map(item => item * 2).filter(item => item % 3 === 0); // this will return [6]

Now let's compare that with the ES5 version:

[1, 2, 3].map(function (item) {
    return item * 2;
}).filter(function (item) {
    return item % 3 === 0;
});

There's an obvious improvement not only in the fact that we can write the same code with less characters, but also in readability. If you don't have any arguments to your function you should use the following syntax:

const func = () => {
    // do something
}

There's a single caveat which you have to take in consideration: unlike the normal anonymous functions which create a new context each one, the arrow functions will keep the context of their parent scope. Developers often had troubles in identifying the context in which their function is running, this should make things a bit easier to understand. Here's an example:

document.body.addEventListener('click', function (event) {
    console.log(this); 
    // 'this' va face referință la elementul body
});
document.body.addEventListener('click', 
 (event) => {
     console.log(this); 
// 'this' va face referință la obiectul Window 
// (adică contextul părintelui) 
});

So, you cannot just do a quick find and replace all your anonymous functions with arrow functions, because it will most certainly break something.

Modules

An anticipated addition to JavaScript was the ability to create and load modules. With the increasing complexity of web applications, it was clear that this feature is a must have, that's why the JavaScript community took it upon itself and created a few solutions for it. The most popular are the CommonJS standard, used by Node JS and Browserify, and Asynchronous Module Definition (AMD), used by RequireJS. The ES6 solution to modules incorporates features from both these popular module systems, but it adds even more useful functionality.

An important change that was made to ES6 modules is that they have a static structure, i.e. the imports will be statically analyzable at compile time. This has a great benefit because linting tools will be able to analyze our code much better.

The ES6 modules syntax gives us multiple ways to export values: named exports (we can have multiple in a module) and default exports (only one per file). The nice part is that we can have both in the same module. Named exports look like this:

// my_module.js
export function helloWorld () {
    console.log('Hello world');
}
export const MY_CONSTANT = 5;
export let loremIpsum = 'dolor';

You also have the option to define all the functions / variables which you want to export and then export them separately like this:

// my_module.js
const helloWorld = () => {
    console.log('Hello world');
}
const MY_CONSTANT = 5;
let loremIpsum = 'dolor';
export {
    helloWorld,
    MY_CONSTANT,
    loremIpsum
}; 

This way it's a bit more visible what you're actually exporting from a module. As a side node, you probably noticed that I've used "let" to declare the "loremIpsum" variable. "let" is also part of the ES6 specification, and it's a new way to declare variables. The difference between "let" and "var" is that "let" is block scoped instead of being function scoped (or global if not in a function), like "var".

That being said, importing from modules looks like this:

// main.js
import { helloWorld, MY_CONSTANT } from 'my_module';
helloWorld(); // va afișa 'Hello world'
// sau importă toate exporturile denumite
import * as m from 'my_module';
m.helloWorld();

Default exports have a sweet and short syntax:

// sum.js
export default function (a, b) {
    return a + b;
};
// main.js
import sum from 'sum';
alert(sum(4, 5)); // va afișa '9'

Having both named exports and a default export in the same module is also possible, and it looks like this:

// module.js
const func1 = () => {
    ...
};
const func2 = () => {
    ...
};
// Exportă valoarea default
export default function () {
    ...
};
// Exportă valorile denumite
export {
    func1,
    func2
};
// main.js
import defaultFunction, 
  { func1, func2 } from 'module';

There is a drawback to this import syntax: it cannot be used inside a script tag (since it's a synchronous tag) and you cannot conditionally load a module. There is, however, an alternative way to load modules, using the module loader API. The syntax looks like this:

System.import('my_module').then(my_module => {
    my_module.helloWorld();
}).catch(error => {
 alert('Cannot load module');
});

Promises

Due to JavaScript's asynchronous nature, we often find ourselves in what is known as the "callback hell", i.e. lots of callbacks in callbacks which reduce readability a lot:

doSomething(param1, param2, () => {
    doSomethingElse((param3) => {
        doMore((param4) => {
            andMore((param5) => {
                // Unde sunt?
            });
        });
    });
});

I think one can see how things can get ugly quite fast, and most of the JavaScript developers have encountered situations like this. Promises are an alternative to callbacks in order to tackle asynchronous code. Just like modules, there are quite a few open source implementations for promises, like Q, RSVP or Bluebird. The popular jQuery has a Deferred object, which offers the same kind of functionality but its syntax is different and it isn't ES6 compliant.

Promises are basically objects which can be in one of the following 3 states:

Every Promise receives a callback function as a parameter. This callback function gets two parameters: a fullfill function, i.e. the operation finished successfully, and a reject function, i.e. the operation failed.

The basic usage of promises looks something like this:

// Define an async function (like an ajax call for example) which returns a promise
const myAsyncFunction = () => {
    return new Promise((fulfill, reject) => {
        // do something
        if (...) {
            fulfill(param1, param2);
        } else {
            reject(error);
        }
    });
}

// Call the async function
myAsyncFunction().then((param1, param2) => {
    console.log(‚async function finished’);
}).catch((error) => {
    console.log(‚Something bad happened: ‚ + error); 
});

The great part of promises is that they are chainable which really flattens out the code:

myAsyncFunction().then(() => {
    // myAsyncFunction finished successfully
    return anotherAsyncFunction();
}).then(() => {
    // anotherAsyncFunction finished successfully
}).catch((error) => {
    // either myAsyncFunction or anotherAsyncFunction failed
});

Classes

Yes, classes finally arrived in JavaScript. As much as it is an anticipated feature, it is also a controversial one because there are quite a lot of developers who oppose the introduction of classes, since javascript already has prototypal inheritance which can also provide classical inheritance.

Classes in ES6 are mostly syntactic sugar, since the same kind of functionality could have been created using ES5, but introducing them will allow future editions of ECMAscript to add more functionality to them. There are already some intentions to extend the classes in ES7 by adding private variables and other features.

The syntax for classes looks like this:

class Vehicle {
    constructor(type, color) {
        this.type = type;
        this.color = color;
    }

    getColor() {
        return this.color;
    }
}

This will create a "Vehicle" class with a constructor and a "getColor" method. You can also extend classes:

// Extending the Vehicle
class Car extends Vehicle {
    constructor(color, maxSpeed) {
        super(‚car’, color);
        this.maxSpeed = maxSpeed;
    }

    getMaxSpeedFormatted() {
        return this.maxSpeed + ‚km/h’;
    }
}

let car = new Car(‚blue’, 200);
console.log(‚We have a ‚ + car.getColor() + ‚ car with a max speed of ‚ + car.getMaxSpeedFormatted());

You'll notice that we're using "super(...)" here to call the parent constructor. Most of the object oriented languages give you this possibility as an option, but in JavaScript this is required if you are overriding the constructor. You have to call the super constructor before trying to access "this", otherwise you'll get an "Uncaught ReferenceError: this is not defined":

class Car extends Vehicle {
    constructor(color, maxSpeed) {
        maxSpeed *= 0.6214; // transform to miles. You can do this before calling ‚super’
        this.maxSpeed = maxSpeed; // but you cannot reference to ‚this’. This will throw a ReferenceError
        super(‚car’, color);
    }
}

You can call other methods on the base class, not only the constructor, using "super.methodName()".

But that's not all!

There are a lot of new features in ES6 you have to know about, like the new way to declare variables using "let" or default values for function arguments, a lot of new methods on the String's and Array's prototypes, and much more. This was just a higher overview and I hope I succeeded in making you want to know more.

For a more extensive list of the new features in ES6, see the actual language specification here.

Another great resource if you want to get into ES6 is Dr. Axel Rauschmayer's book "Exploring ES6" which he kindly offers for free in online version here or you can buy the book to support his work here.

How to use ES6 today

Well, all these features are shiny and great, but how could we use them today? ES6 was officially approved as of June 2015, but we all know it will take time until the browsers will fully implement all the features. Evergreen browsers are catching up quite fast, but even if they will implement ES6 fully, most of us still have to support older browser versions (we all know I'm talking about Internet Explorer here). So, how can we use all, or at least a subset, of these great features today?

The solution? Transpilers! There are quite a few ES6 to ES5 transpilers available at the moment, with Babel JS and Traceur being the most popular. An up to date list with the most used transpilers and their feature support (plus browser support) can be found at kangax's compatiblity table . At the moment of writing this article, Babel JS (which has evolved a lot meantime and is more a platform than a transpiler now, see http://babeljs.io/blog/2015/10/29/6.0.0/) has the best support, with over 71% of the features being supported, which is pretty good for a transpiler!

Babel, and most of the other transpilers, also have gulp (and Grunt) plugins available, so it's easy and quick to set them up right into your build system. Take a look at this short and minimal example of using Babel with gulp:

var gulp = require(‚gulp’);
var babel = require(‚gulp-babel’);

gulp.task(build-js, function () {
    return gulp.src(‚src/app.js’)
        .pipe(babel())
        .pipe(gulp.dest(‚dist’));
});

Conclusion

ES6 definitely brought us lots of improvements, from simple syntactic sugar (like classes) to new and complex features, such as promises and modules. Some of the new features are game changers (e.g. modules, promises, etc.), some will make our code cleaner and more readable (e.g. arrow functions, template strings, etc.), others are simply useful (e.g. new methods for arrays).

This new release of ECMAScript will not only change the way we write JavaScript, but it will also change the way we think and organize our JavaScript code. Simply put, it will make our lives easier.

Suggested links

http://www.ecma-international.org/ecma-262/6.0/

http://exploringjs.com/es6/

https://github.com/lukehoban/es6features

Sponsors

  • comply advantage
  • ntt data
  • 3PillarGlobal
  • Betfair
  • Telenav
  • Accenture
  • Siemens
  • Bosch
  • FlowTraders
  • MHP
  • Connatix
  • MetroSystems
  • BoatyardX
  • Colors in projects