My favourite new features in ES6
My favourite new features in ES6

Nov 2016

I really love the whole JavaScript community, from the excellent and innovative open source libraries (e.g. d3.js), the ability to run code on various devices (react-native), amazing full-stack development options (meteor.js) and even databases (mongodb). We're now seeing the implementation of ES6 and I'm pretty excited about the new features we're getting. I particularly love the addition of class (I'm an OOP guy at heart) and Promise (this just makes life easier). In this post I'll get you up and running with ES6 and run through some examples of the top new features in ES6.


Getting started

Before we can jump in and get our hands dirty we need to have a mechanism for converting this new ES6 JavaScript into a version that will run in the browser / node. This stage is called transpilation and we'll use a tool called gulp to run a transpiler called Babel.

Transcompilers

The 2 most popular transcompilers at the moment are Babel and Traceur. In this post we’ll be using the Babel transcompliler to convert our ES6 JavaScript to ES5 Javascript, which will ensure that it can run in any JavaScript engine.

npm init
npm install --save-dev gulp-babel babel-preset-es2015

Then create a file in your project called .babelrc and paste this text into it:

{ "presets": ["es2015"] }

With this file the Babel transcompiler will know that you’re using ES6.

Gulp

Gulp is my go to build tool and we’ll use it to run the Babel transcompiler. Install Gulp by running:

$ sudo npm install -g gulp

You need to install Gulp globally once. Then, for each project, you’ll need a local Gulp. So from the root directory of your project run

$ npm install --save-dev gulp

Create the file gulpfile.js and stick the following in there

const gulp = require('gulp');
const babel = require('gulp-babel');
gulp.task('default', function(){
    // node source
    gulp.src("es6/**/*.js")
        .pipe(babel())
        .pipe(gulp.dest("dist"));
    // browser source
    gulp.src("public/es6/**/*.js")
        .pipe(babel())
        .pipe(gulp.dest("public/dist"));
});

Now we’ll create a .js file and check that we can transcompile and run it. So create a directory called es6 and in there create a file called test.js and paste in the following simple code:

'use strict';
const sententaces = [
    {subject: 'ES6', verb:'is', object:'sweet'},
    {subject: 'ES5', verb:'is so', object:'last year'}
];
function say({subject,verb,object}){
    console.log(`${subject} ${verb} ${object}`);
}
for(let s of sententaces){
    say(s);
}

run gulp with:

$ gulp

the transcompiled code has been saved to the dist directory (we told it to do this in the gulpfile) and you can run it with the following command:

$ node dist/test.js

Your output should look like this:

ES6 is sweet
ES5 is so last year

There we go; our first bit of ES6. Before we go any further, we’ll add a watcher to our gulp stack. This will watch for changes to our ES6 files and automagically run gulp, saving us the hassle.

const gulp = require('gulp');
const babel = require('gulp-babel');
gulp.task('default', function(){
    // node source
    gulp.src("es6/**/*.js")
        .pipe(babel())
        .pipe(gulp.dest("dist"));
    // browser source
    gulp.src("public/es6/**/*.js")
        .pipe(babel())
        .pipe(gulp.dest("public/dist"));
});
gulp.task('watch', ['default'], function () {
    gulp.watch(["es6/**/*.js", "public/es6/**/*.js"], ['default']);
});

Now you can just run $ gulp watch to have Babel run whenever gulp detects a change to any .js files in the es6 and public/es6 directories (if you notice that your transcompiled javascript is stale check that the gulp watcher hasn't crashed).

Variables and Constants

Make const not var. John Lennon and Yoko Ono.

The value of a variable can change at any time whereas the value of a constant is assigned when it is created and cannot be changed after that. When you don’t need to change the value of something then get into the habit of using const. If you do need to reassign the value then use the new keyword let in favour of the old var. The difference is very subtle but will save you from the occasional nightmare of a debug scenario like this:

function varTest() {
  var x = 1;
  if (true) {
    var x = 2;  // same variable!
    console.log(x);  // 2
  }
  console.log(x);  // 2
}

Using let instead generally gives you the behaviour you’d expect:

function letTest() {
  let x = 1;
  if (true) {
    let x = 2;  // different variable
    console.log(x);  // 2
  }
  console.log(x);  // 1
}

In the real world, this is a particular issue when executing asynchronous code. Take the following simple example:

var i;
for (i = 3; i >= 0; i--) {
    setTimeout(function () {
        console.log(i);
    });
}
// prints out
// -1
// -1
// -1
// -1

Because the function getting passed to setTimeout is invoked at some point in the future, by the time it runs i is -1. The ES5 work around is to use an additional function to create a new scope, which captures the value of i in a closure.

// old-school workaround using a closure and an IIFE (immediately invoked function expression)
var i;
for (i = 3; i >= 0; i--) {
    (function (i) {
        setTimeout(function () {
            console.log(i);
        });
    })(i);
}
// prints out
// 3
// 2
// 1
// 0

This is a pain in the ass and pretty fatiguing on the developer. Block-scoped variables solve this problem for us:

// Use 'let' instead
for (let i = 3; i >= 0; i--) {
    setTimeout(function () {
        console.log(i);
    });
}
// prints out
// 3
// 2
// 1
// 0

Destructuring

This feature lets you take an object or array and destructure it into individual variables.

Destructuring objects

Create the file es6/destructuring.js :

"use strict";
const obj = {b:2, c:4, d:"hello, world"};
const{a,b,c} = obj;
console.log(a);
console.log(b);
console.log(c);

Run $ node dist/destructuring.js and you'll get the following output:

undefined
2
4

Note: For brevity, I’ll now stop telling you to create file abc.js and run it through node. You get the idea!

Destructuring arrays

You can also destructure arrays:

const arr = ["I'm", "an", "array"];
let [x,y] = arr;
console.log(x); // I'm
console.log(y); // an

The spread operator lets you take all the remaining elements. Note that this will work on arrays but not objects:

let [z, ...theRest] = arr;
console.log(z); // I'm
console.log(theRest); // [ 'an', 'array' ]

Destructuring arguments

We can even do this stuff on function args!

function average({a, b}){
    return (a + b) / 2;
}

const o = {
    a: 4,
    b: 6
};

average(o); // 5

Note that the identifiers in the supplied object must match up with the argument names in the function.

Template strings

This is a small addition to the language, compared to some of the other new features, but I really like how it makes writing strings neater:

"use strict";
const name = "ninjaPixel",
  o = {
      day: {
          ofWeek: "Sunday",
          ofMonth: "9"
      },
      month: "October"
  };

function ordinalIndicator(x) {
    switch (x) {
        case 1:
            return "st";
        case 2:
            return "nd";
        case 3:
            return "rd";
        default:
            return "th";
    }
}

const out = `This is ${name}'s blog. It was written on ${o.day.ofWeek} ${o.day.ofMonth}${ordinalIndicator(o.day.ofMonth)} of ${o.month}`;
console.log(out);

This isn't anything that we couldn't have done in ES5, it's just neater. Note how we use ${...} to access variables and functions and the whole template string is encapsulated in backticks.

This is ninjaPixel's blog. It was written on Sunday 9th of October

Default arguments

This is a seriously handy feature. With ES5 you'd have to explicitly handle undefined args and assign default values, within your function block. Specifying these default values when you define your function is much neater and it's also much easier to understand when reading someone else's code.

function x(a, b = "Vote Pedro!", c = 1){
    return `${a} - ${b} - ${c}`;
}

x(1,2,3); // "1 - 2 - 3"
x(1,2); // "1 - 2 - 1"
x(1); // "1 - Vote Pedro! - 1"
x(); // "undefined - Vote Pedro! - 1"

Fat arrows

Officially called 'arrow notation', fat arrows basically save you from typing boilerplate code when declaring functions. There is also the benefit that this is lexically scoped within arrow functions, saving you from having to do the classic var self = this trick that you see all over the shop in ES5.

const func1 = function(){return "Hello, world!";};
// or with fat arrows:
const func1 = () => "Hello, world!";

const func2 = function(name){return `Hello, ${name}`;};
// or with fat arrows:
const func2 = (name) => `Hello, ${name}`;

const func3 = function(x, y) {return x + y;};
// or with fat arrows:
const func3 = (x, y) => x +y;

const o = {
    name: "Matt",
    greet:
}

"use strict";
const skinny = {
      name: "matt",
      greet: function(){
          function capitaliseName(){
              return this.name.charAt(0).toUpperCase() + this.name.slice(1);
          }
          return `Hello, ${capitaliseName()}!`;
      }
  };

  const fatty = {
      name: "matt",
      greet: function(){
          const capitaliseName =() => {
              return this.name.charAt(0).toUpperCase() + this.name.slice(1);
          }
          return `Hello, ${capitaliseName()}!`;
      }
  };

  console.log(skinny.greet()); // Cannot read property 'name' of undefined
  console.log(fatty.greet()); // Hello, Matt!

Note that you can omit the return statement and curly braces when the function body is a single expression.

Object-oriented programming

Yes. Finally. JavaScript has OOP.

Rather than go into detail about what OOP programming is (there are already loads of better blogs on that), let's jump straight into using it:

class CrapSuperhero {
    constructor(name, power) {
        this._name = name;
        this._power = power;
        this._clothes = 'casual';
    }

    run() {
        console.log(`${this._name} is running`);
    }

    // we can use getters and setters to protect the way that internal variables are written to
    get clothes() {
        return this._clothes;
    }

    set clothes(clothes) {
        if (typeof clothes === 'string') {
            this._clothes = clothes;
        } else {
            throw new Error('Clothes must be typeof string');
        }
    }

    // static method
    static getNextAvailableSuperheroID() {
        return CrapSuperhero.nextID++;
    }
}
CrapSuperhero.nextID = 0;

// instantiation
const manSpider = new CrapSuperhero('man spider', 'Overly hairy legs');
const manIron = new CrapSuperhero('man iron', 'Expert at ironing shirts');

manSpider.run(); // man spider is running
manIron.run(); //man iron is running

// using getters and setters
manIron.clothes = 'steam iron';
manSpider.clothes = function () {}; // Error: Clothes must be typeof string

// call static method
const id = CrapSuperhero.getNextAvailableSuperheroID(); // 0

Doing inheritance

class Animal {
    constructor(name) {
        this._is_motile = true; // can move about the place
        this._has_rigid_cell_walls = false;
        this._is_heterotrophic = true; // digests food internally
        this._name = name;
    }

    respondToStimuli(stimuli) {
        console.log(`${this._name} is responding to ${stimuli}`);
    }

    move() {
        console.log(`${this._name} is moving`);
    }


}

class Mammal extends Animal {
    constructor(name) {
        super(name);
        this._has_sweat_glands = true;
        this._has_jaw_joint = true;
    }

    regulateTemperature() {
        console.log(`${this._name} is using hair to regulate temperature`);
    }

    set fur(color){this._fur =`${color} fur`;}
    get fur(){return this._fur;}
}

class Gorrilla extends Mammal {
    constructor(name, color) {
        super(name);
        this.fur=color;
        this._endangered = true;
    }

    hunt(prey) {
        console.log(`${this._name} is hunting ${prey}`);
    }

    move() {
        console.log(`${this._name} is walking on knuckles`);
    }
}

const harambe = new Gorrilla('Harambe', 'silver');
harambe.respondToStimuli('cold temperature'); // Harambe is responding to cold temperature
harambe.regulateTemperature(); // Harambe is regulating temperature
harambe.move(); // Harambe is walking on knuckles
harambe.fur; // silver fur

Enumerating properties

for(let p in harambe){
    console.log(`${p} = ${harambe[p]}`);
}

// output:
_is_motile = true
_has_rigid_cell_walls = false
_is_heterotrophic = true
_name = Harambe
_has_sweat_glands = true
_has_jaw_joint = true
_fur = silver fur
_endangered = true

toString

Every object ultimately inherits from Object. The toString method returns [object Object] which isn't particularly useful. However, you can overide this method simply like so:

class HelloWorld {
    constructor(){
        this.prop1='Hello';
        this.prop2=', World!';
    }

    toString(){
        return `prop1: ${this.prop1}. prop2:${this.prop2}`;
    }
}

let hw = new HelloWorld();
hw.toString(); // prop1: Hello. prop2:, World!

Mixins

Justin Fagnani's post on ES5 mixins is phenomenal and he comes up with a really nice way to descriptively assign them to your class. Check his blog out for a great write up. I've pasted the crux of it below:

// from http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/
let Mixin1 = (superclass) => class extends superclass {
  foo() {
    console.log('foo from Mixin1');
    if (super.foo) super.foo();
  }
};

let Mixin2 = (superclass) => class extends superclass {
  foo() {
    console.log('foo from Mixin2');
    if (super.foo) super.foo();
  }
};

class S {
  foo() {
    console.log('foo from S');
  }
}

class C extends Mixin1(Mixin2(S)) {
  foo() {
    console.log('foo from C');
    super.foo();
  }
}

new C().foo();

Which outputs:

          from C
          from Mixin1
          from Mixin2
          from S

Nicer assignment of the mixin:

let mix = (superclass) => new MixinBuilder(superclass);

class MixinBuilder {
  constructor(superclass) {
    this.superclass = superclass;
  }

  with(...mixins) {
    return mixins.reduce((c, mixin) => mixin(c), this.superclass);
  }
}

class MyClass extends mix(MyBaseClass).with(Mixin1, Mixin2) {
   /* ... */
 }

This enables us to now write:

class C extends mix(S).with(Mixin1, Mixin2) {
    foo() {
    console.log('foo from C');
    super.foo();
  }
}

Maps

To make key-value pairs is ES5 you'd use an object. ES6 brings us a data structure exclusively for doing this. maps overcome the following drawbacks of using objects for key-value pairs:

  • Keys must be strings or symbols, meaning that you can't map an object to a value.
  • Objects guarantee no order to their properties.
  • The prototypal nature of objects means there could be mappings that you didn't intend
  • There's no easy way to know how many keys there are in an object.

Usage:

const user1 = {id:1, name:'Bob';};
const user2 = {id:2, name:'Mary';};

const roles = new Map();

roles
    .set(user1, 'Admin')
    .set(user2, 'Trial');

roles.get(user2); // 'Trial'
roles.has(user1); // true
roles.get({foo:'bar'}); // undefined
roles.size; // 2

Sets

Like an array but duplicates are not allowed. Continuing from our users and roles example, it wouldn't make sense if a given user had the same role multiple times, so we could use a set instead:

const possibleRoles = new Set();
possibleRoles
    .add('Trial')
    .add('Admin'); // Set['Trial', 'Admin']

// you don't need to check if an item already exists before adding it
possibleRoles.add('Trial'); // nothing happens. Set['Trial', 'Admin']
possibleRoles.size; // 2

possibleRoles.delete('Admin'); // true is returned as the item existed in the set
Please enable JavaScript to view the comments powered by Disqus.