For our last lesson in this course, we'll be doing a roundup of all the features so you can get a recap and also see some of the smaller features.

ECMAScript 2015 (ES6), is an essential version of JavaScript that should be mastered by the every JavaScript developer. While there are a handful of differences in ES6 compared to previous versions of JavaScript, a lot of code you are going to run into as a developer will be written in ES6. Knowing some of these new features can be a huge advantage.

The list of features to learn in ES6 are not exhaustive, however we will only cover the key features in ES6 that we feel every JavaScript developer must know. These are:

Table of Contents

    1. Promises in ES6
    2. Variable Declaration in ES6
    3. Multi-line Strings in ES6
    4. Destructuring Assignment in ES6
    5. Object Literals in ES6
    6. Arrow Functions in ES6
    7. Default Parameters in ES6
    8. Template Literals in ES6
    9. Classes in ES6
    10. Modules in ES6

    Promises in ES6

    Until promises arrived, JavaScript developers had been using callbacks. Chances are you've used callbacks at one point without even knowing it. setTimeout, XMLHttpRequest, and basically all browser-based asynchronous functions are callback based.

    Let's check out this example of a delayed asynchronous execution with setTimeout():

    setTimeout(function() {
      console.log('Cool!')
    }, 1000);

    The above code can be written in ES6 as:

    let lunchTimeDelay1000 = new Promise(function(eat, skip) {
      setTimeout(eat, 1000);
    }).then(function() {
      console.log('Lunch Time!');
    });

    Or with ES6 arrow functions:

    let lunchTimeDelay1000 = new Promise((eat, skip) => setTimeout(eat, 1000))
                                    .then(() => console.log('Lunch Time!'));

    It's important to note that promises become more useful as we accumulate more logic inside our functions. Take a good look at the bit of code written with ES6 promises below:

    var lunchTimeDelay1000 = () => new Promise((eat, skip) => { setTimeout(eat, 1000) })
    
    lunchTimeDelay1000()
      .then(function() {
        console.log('Lunch Time!');
        return lunchTimeDelay1000();
      })
      .then(function() {
        console.log('Good Food!');
      });

    It's obvious that promises are useful when we want to run some code, wait for some time, run some more code, wait again and run some more code again.

    Variable Declaration in ES6

    Before ES6, to declare variables, you could only use the var keyword.

    var x;
    var y = 5;

    In ES6 it's different. You can declare variables using const or let. let is like a new var which is block-scoped unlike var which is function-scoped, variables declared with let can be reassigned. Check out the example using var below:

    function calculateSalary(job) {
      var income = 5;
    
      if (job) {
        var income = 50;
      }
    
      { 
        var income = 500;
    
        {
          var income = 5000;
        }
      }  
    
      return income;
    }
    
    console.log(calculateSalary(true));

    In the code above, due to the fact that var is function scoped. The blocks had no effect on the var and our result will be 5000. Let's take a look at the same bit of code using let this time.

    function totalSalary(job) {
      var income = 5 // it is possible to use var alongside let
    
      if (job) {
        let income = 50 // doesn't change the value of the first income, it's still 5
      } 
    
      {
        let income = 500 // first income is still 5
    
        {
          let income = 5000 // first income is still 5
        }
      }
    
      return income;
    }
    
    console.log(totalSalary(true));

    The value is 5 because the if block also has let. If it had nothing or var, it would have been 50.

    const is a fixed, read-only identifier, its variable cannot be reassigned. Check out the code block below:

    function totalSalary(job) {
      const income = 7;
    
      if (job) {
        const income = 70;
      }
    
      {
        const income = 700;
    
        {
          const income = 7000;
        }
      }
    
      return income;
    }
    
    console.log(totalSalary(true));

    The value is 7 because like let, const is also block scoped. const and let are more expressive than var because they specify whether we plan to change the value of a variable or not. Asides from being more expressive, they are also scoped which means they can be used inside of an if branch, or a for loop and can only be accessed within them.

    Multi Line Strings in ES6

    In ES5, we had to add a backslash at the end of each line to tell the JavaScript engine that the string would continue in the next line. Check out the code block below:

    const drSeuss = 'My name is Sam I Am,\n' + 'I do not like green eggs and ham,\n' + 'Lunchtime is here. Come and eat'

    In ES6, we simply make use of backticks:

    const drSeuss = `My name is Sam I Am, I do not like green eggs and ham, Lunchtime is here. Come and eat`

    ES6 template strings also let us create multiline strings:

    const drSeuss = `
      My name is Sam I Am
      I do not like green eggs and ham
      Lunchtime is here
      Come and eat
    `;

    ES6 template strings also let us use variables really easily:

    const myName  = 'Sam';
    const drSeuss = `
      My name is ${myName} I Am
      I do not like green eggs and ham
      Lunchtime is here
      Come and eat
    `;

    Destructuring Assignment in ES6

    Destructuring let's you extract multiple values from data stored in objects and arrays. Check out this example of destructuring in ES5:

      var food = $('menu').food(), // food has properties fish and meat
      fish = data.fish,
      meat = data.meat

    Let's take a look at this Node.js example on destructuring assignments:

      const jsonMiddleware = require('body-parser').json;
    
      // get username and password from the 
      const username = req.body.username;
      const password = req.body.password;

    In ES6, the ES5 code above will look like this:

    // example 1
    const { fish, meat } = $('menu').food(); // we'll get fish and meat variables
    
    // example 2
    const { json: jsonMiddleware } = require('body-parser');
    const { username, password }   = req.body;

    Object Literals in ES6

    Improvements in object literals were made in ES6 in a few ways which are:

    • Setup the prototype on object construction
    • Shorthand method declarations
    • Make super calls
    • Computed property names

    Here's a typical ES5 object literal with some methods and properties:

    var objectManager = {
      port: 3000,
      url:  'manage.com'  
    };
    
    const getAccounts = function() {
      return [1, 2, 3];
    }
    
    var manageObjectES5 = {
      port:        objectManager.port,
      url:         objectManager.url,
      getAccounts: getAccounts,
      toString: function() {
        return JSON.stringify(this.valueOf());
      },
      getUrl: function() {
        return `http://${this.url}:${this.port}`;
      },
      valueOf_1_2_3: getAccounts();
    };

    The same object literal in ES6 will have shorthands for the assignment getAccounts, also we set our prototype right there in the _proto property:

    const objectManager = {
      port: 3000,
      url: 'manage.com'
    };
    
    const getAccounts = function() {
      return [1, 2, 3]
    };
    
    const manageObject = {
      proto: objectManager,
      getAccounts,
      // Also, we can invoke super and have dynamic keys (valueOf_1_2_3):
      toString() {
        return JSON.stringify((super.valueOf()))
      },
      getUrl() {
        return "http://" + this.url + ':' + this.port
      },
      ['valueOf_' + getAccounts().join('_')]: getAccounts()
    };
    
    console.log(manageObject);

    Arrow Functions in ES6

    Also known as "fat arrow" functions, the arrow function is a new, more brief way of writing functions in ES6. Check out this brief example of a function in ES5:

    var stringLowerCase = function() {
      var _this   = this;
      this.string = this.string.toLowerCase();
    
      return function() {
        return console.log(_this.string);
      }
    };
    
    stringLowerCase.call({
      string: 'JAVASCRIPT'
    })()

    In ES6, _this can eliminated as it is no longer needed because arrow functions help bind to the correct context:

    const stringLowerCase = function() {
      this.string = this.string.toLowerCase();
      return () => console.log(this.string);
    }
    
    stringLowerCase.call({
      string: 'JAVASCRIPT'
    })()

    As seen above, it's still possible to use function from ES5 with => in ES6. When an arrow function is used with one line statement, it implicitly returns the result of that single statement. For situations with more than one line, you'll need to use return explicitly.

    The below statements are the same:

    // es5
    var sayHello = function() {
      return 'hello!';
    };
    
    // es6 with explicit return
    let sayHello = () => {
      return 'hello!';
    };
    
    // es6 with implicit return
    let sayHello = () => 'hello';

    Default Parameters in ES6

    With default parameters, we can initialize functions with default values. A default parameter can be anything from a number to another function. Check out this example of default parameters in ES5:

    var garage = function(model, color, car) {
      var model = model || 'Mustang'
      var color = color || 'blue'
      var car = car || 'Ford'…
    }

    In ES6, Instead of using the logic OR in our default parameters, we can put the default values right into the signature of the functions:

    var garage = function(model = 'Mustang', color = 'blue', car = 'Ford') {
      // ...
    }

    Template Literals in ES6

    Template Literals allow us to output variables in the string. In ES5, we used inverted commas to create template literals. Check out the example below:

    var x = 10;
    var y = 20;
    
    console.log('Thirty is ' + (x + y));

    ES6 changes all of that with backticks and placeholders that are indicated by the dollar sign and curly braces (${example). Check out this example below:

      var x = 10; 
      var y = 20; 
      console.log(`Thirty is ${x + y}`)

    Classes in ES6

    Creating classes and using them in ES5 was difficult because there wasn't a keyword. ES6 classes solve all of that with the use of prototypes. In the example below, we have a class carGarage in which we define a constructor and a getCarModel() method:

    class carGarage {
    
      // our class constructor 
      constructor(price = {}, mileage = []) { 
        this.car     = 'Ford';
        this.model   = 'Mustang';
        this.color   = 'blue';
        this.price   = price;
        this.mileage = mileage;
      }
    
      // our class method
      getCarModel() { 
        console.log(`Car model: ${this.model}`);
      }
    }

    Notice that we use default parameter values for price and mileage. Also, method names don't need to have the word function or the colon (:) anymore. Also, we can't assign the property this.model the same way as methods meaning we can't say name at the same indentation level as a method. To set the value of a property, we simply assign a value in the constructor.

    Suppose we want another class to inherit from garage, ES6 comes with the extends keyword which allows us to create a class as a child of another class. In the example below, the class carDealership extends carGarage:

    class carDealership extends carGarage {
    
      constructor(price, mileage) {
        // To call the parent constructor we use the super keyword
        super([15000, 50000])
        this.name = 'Ford Mustang';
        this.year = 2012;
      }
    
    }
    
    let carSale = new carDealership();
    
    carSale.getCarModel();                            // Car model: Mustang 
    console.log(`Data is ${price} and ${mileage}`);   // Data is 15000 and 50000

    Modules in ES6

    Before ES6, JavaScript had no support for modules save for a few bypasses such as AMD, RequireJS and CommonJS. Now we have modules with import and export operands. In ES5, it was normal to use <script> tags with immediately invoked function expressions (IIFE) or library like AMD.

    In ES6, we can expose our classes with export. In the example below, let's say we have port variable and getCar method in ES5 file module.js:

    module.exports = {
      port:   3000,
      getCar: function() {…}
    }

    In another of our ES5 files main.js, we would require('module') that dependency:

    var service = require('module.js'); 
    console.log(service.port); // 3000

    In ES6 the need for all that is eliminated, we would use export and import. Below is our library in the ES6 module.js file:

    export var port = 3000
    export function getCar(model) {…}

    The ES6 file main.js acts as our importer, we use import {name} from 'my-module' syntax. For example:

    import {port, getCar} from 'module'; 
    console.log(port); // 3000

    Alternatively, we can import everything as a variable service in main.js:

    import * as service from 'module'; 
    console.log(service.port); // 3000

    Conclusion

    ES6 is finalized, but not fully supported by all browsers. To use ES6, you will have to get a compiler like Babel. Babel can be run as a standalone tool or with your build system ― Babel can be used as a plugin with Webpack.

    For more on transpilers: JavaScript Transpilers: What They Are and Why We Need Them

    Chris Sevilleja

    176 posts

    Founder of Scotch.io. Google Developer Expert in Web Technologies. Slapping the keyboard until something good happens.