Over a million developers have joined DZone.

ES6 Crash Course

Officially known as ECMAScript 2015, this new standard on which JavaScript is based should be on everyone's shortlist of things to learn. Get a play-by-play crash course as both the author and reader go through the learning process.

· Web Dev Zone

Start coding today to experience the powerful engine that drives data application’s development, brought to you in partnership with Qlik.

Yes, this is another article about learning ES2015 (known as ES6). It's my journey of learning it, as well as my personal notepad for new stuff I discover along the way. Feel free to suggest new or missing sections or contribute to this article. It's open source and on GitHub.

Browser Compatibility

ES6 or ES2015 how it's officially called now has been released on June 2015. As such browsers start implementing its features. Some are already at a good level which you can easily online. Kangax is one example of showing the current browser support.

Regardless of browser support, from now on, people will have to rely on transpilers which make sure your code runs smoothly even if the underlying browser does not fully support all of the latest language features. Why's that? Well, with the name ES2015, the TC39 committee clearly suggests more frequent, yearly releases. In fact, ES7 or better ES2016 is already on it's way.

The most popular transpilers are for sure

  • BabelJS - formerly ES6-to-5 and used in this tutorial
  • Traceur

Addy Osmani has a more complete list of es6-tools on GitHub.

Tooling

Probably the easiest way to try ES6 now, without having to setup your local workstation is on jsbin.com.

Set ES6/Babel on jsbin.com

Simply create a new "bin", and choose "ES6/Babel" as your language.

Ok, so now we're setup and ready to get started.

Block scoping with let

ES5 defines scoping of variables quite different than many of the other popular languages which are currently out there. This causes some trouble, especially for novice programmers, but not only. ES5 has so-called function scope rather than block scope as you'd expect.

function someFunc(){
  console.log('entering in someFunc');
  if(true){
    var x = 'Hi there';
  }

  console.log(x);
}

This is totally legitimate JavaScript code and prints the following to the console:

"entering in someFunc"
"Hi there"

As you can see, x is visible also outside the if block. While the example above is quite clear, this can lead to quite strange and hard to read code. What's the output of the code?

var foo = 1;
function bar() {
    if (!foo) {
        var foo = 10;
    }
    console.log(foo);
}
bar();

It prints: 10.

By using let, ES6 allows you to block scope your variable, just as you would expected. Let's adapt the above example by changing var to let:

function someFunc(){
  console.log('entering in someFunc');
  if(true){
    let x = 'Hi there';
  }

  console.log(x);
}
someFunc();

This time, we get an error. Obviously (would the Java programmer say).

"entering in someFunc"
"error"
"ReferenceError: x is not defined
    at someFunc (fitusuzuhi.js:9:40)
    at fitusuzuhi.js:11:1
    at https://static.jsbin.com/js/prod/runner-3.29.17.min.js:1:13603
    at https://static.jsbin.com/js/prod/runner-3.29.17.min.js:1:10524"

While you can still use var, the new suggested approach is to switch to let. So...start to get accustomed.

Try it out yourself: click here

Redefining Wariables in Your Code? Not With ES6, Bad Boy!

In ES5, this is totally legitimate:

var name = 'Juri';
var name = 'Thomas';

console.log(name);

Obviously, "re-using" variables is not something you'd want to do but rather there's a good chance you introduced it by accident. I guess you can imagine nasty bugs resulting from this. Luckily ES6 will throw an error when you redefine the same variable within the same block. You have to use let, however.

let name = 'Juri';
let name = 'Thomas';

console.log(name);

The result:

Image title

Error when re-defining let variables

Instead, redefining the variable within a subscope works, and results in overriding the previously defined one:

(function(){
  let name = 'Juri';
  console.log(name);

  function innerFunc(){
    let name = 'Thomas';
    console.log(name);
  }
  innerFunc();

})();

The output is:

"Juri"
"Thomas"

Try it out yourself: click here

True Constants With const

Constants in ES5 were nothing more than simple naming conventions, like prefixing a variable with const: var CONST_PI = 3.141. Nothing hindered you however to change the value at runtime.

In ES6 finally there's a const keyword that does what you'd expect.

const pi = 3.141;
pi = 12;

It throws an exception at runtime if you try to write on the variable.

Image title

Error when trying to set const fields

Watch out however, you can also assign objects to const variables. What is being assigned is the object reference, though. Hence, changing the object reference by assigning another object to the variable won't work, but you can definitely change the object's properties.

const myObjConst = {
  name: 'juri'
};

// totally working as you don't
// change the obj reference
myObjConst.name = 'Thomas';

// this will break
myObjConst = { name: 'Thomas' };

Try it out yourself: click here

The scope of const variables is block scope, which totally makes sense.

... Operator: Spread and Rest Spread

Do you remember about Function.prototype.apply and Function.prototype.call. Not sure how many times I googled for the "apply vs. call" article as I couldn't remember which of the two accepts the array and which one the param series.

apply was commonly used to forward an unknown number of function arguments to another function.

var wrapper = function(){
  return anotherFunction.apply(null, arguments);
}

There are different reasons why one would do this, but I don't want to go into the details of it now. What's important here is that the above code is everything else than readable, right? One immediately thinks: "what does apply do?" and "where does 'arguments' come from?". Overall, it's hard to understand by a non-JavaScript experienced developer.

The spread operator allows us to write our example in a much more elegant way:

var wrapper = function(...args){
  return anotherFunction(...args);
}

Now it's clear that we're invoking function anotherFunction and that we pass along a variable number of arguments that has been passed in by someone else.

Not only is the spread operator useful for function calls, but also when manipulating arrays. Here's a simple scenario: do you know how to concat two arrays in JavaScript?? Like, having one array and pushing the elements of another one into the first?

// ES5 code
var names = [
  'Juri',
  'Steffi',
  'Thomas'
];

var anotherSetOfNames = ['Tom', 'Jack'];
names.push.apply(names, anotherSetOfNames);

// you get
// [ 'Juri',  'Steffi',  'Thomas', 'Tom', 'Jack' ];

The ES6 code:

let names = [
  'Juri',
  'Steffi',
  'Thomas'
];

//adding values
let anotherSetOfNames = ['Tom', 'Jack'];
names.push(...anotherSetOfNames);

So much more readable, isn't it? You could even do this:

let a1 = [1, 4, 5, 2, 3];
let a2 = [1, 2, ...a1, 3, 44, 2]

Try it out yourself: click here

Rest

No, it has nothing to do with REST (Representational State Transfer). The rest parameter is the last one in a sequence of function arguments that captures the "rest of the args".

function myFunction(a, b, ...args){
  //...
}

I'm not even going to detail how to do this in ES5 (let's forget about the past). It had to do with "slicing" from the arguments value the number of args passed to the current function and so on...

Destructuring Assignment

Destructuring assignment is the process of assigning the values of an iterable to variables. PonyFoo also recently published an in-depth guide on destructuring which you might be interested in.

Array Destructuring

let juri, thomas;
let myArray = ['Juri', 'Thomas'];

// destructuring
[juri, thomas] = myArray;

Executing this code, juri and thomas will get the according values from myArray assigned.

Try it out yourself: click here

By combining the destructuring with the ... operator you get even more interesting use cases (also commonly known from functional programming languages). Extracting the head or tail of a list gets extremely easy.

let head, tail;
let names = ['Juri', 'Steffi', 'Thomas', 'Susi'];

[head, ...tail] = names;
// Output:
// head = ['Juri']
// tail = ['Steffi', 'Thomas', 'Susi'];

Try it out yourself: click here

Having this, and with a bit of recursion, a sum function could be defined like..

function sum(numbers){
  let head, tail;
  [head, ...tail] = numbers;

  if(numbers.length > 0){
    return head + sum(tail);
  } else {
    return 0;
  }
}

Try it out yourself: click here

There's even more: you can ignore values when applying the destructuring operator.

let head, tailMinusOne;
let names = ['Juri', 'Steffi', 'Thomas', 'Susi'];

[head, , tailMinusOne] = names;

Object Destructuring

Similar as with arrays, destructuring works with objects as well. Even the syntax is similar:

let name, age;
let person = {
  name: 'Juri',
  age: 30
};

({name, age} = person);

The only thing that might seems strange is the fact you have to wrap the expression with braces.

Try it out yourself: click here

Now, in the example above the variable names have to match the ones from the object. That might not always be the case. But ES6 has a solution.

let x, y;
({name:x, age:y} = { name: 'Juri', age: 30 });

You can make it even shorter (not sure that's what you'd want, though):

let {name: x, age: y} = { name: 'Juri', age: 30 };

Default Values

What if a given value is not present during destructuring? Apply a default!

let a, b;
var numbers = [1];

[a,b=0] = numbers;

// Result: b doesn't have a corresponding value, so it'll be set to 0;

I'm even more excited about default values for function arguments:

function myFunction(a = 0, b = 1, c = 2){
  //...
}

Currently you had to write it like

function myFunction(a, b, c){
  a = a || 0;
  b = b || 1;
  c = c || 2;

  // ...
}

Another interesting use case is to combine default values together with object destructuring.

// ES5
function printPersonInfo(person){
  var name = (person && person.name) || '(not defined)';
  var age =  (person && person.name) || 18;

  console.log(name, age);
}
printPersonInfo({
  name: 'Juri',
  age: 30
  });

This checking is cumbersome, and gets even more weird when having nested objects.

// ES6
function printPersonInfo({name = "(not defined)", age = 18 } = {}) {
  console.log(name, age);
}

printPersonInfo({
  name: 'Juri',
  age: 30
});
//"

Try it out yourself: click here

Links

  • MDN: Destructuring assignment
  • Arrow Functions

    If you're a seasoned JavaScript developer, this should look familiar:

    var that = this;
    $('.someButton').click(function(){
      // stupid example
        that.someVariable = 'hi';
    });
    

    Here it's an example of a jQuery click handler. Notice the var that = this line? This is a workaround you have to deal with - mostly when declaring callbacks - in order to adjust the value of this. The value of this inside the callback points to the function which invoked the callback, and not the outer scope of where the callback has been implemented. Normally, you want to have the latter, which is why "hacks" like var that = this or var self = this are being used.

    Try it out yourself: click here

    jQuery even has a jQuery.proxy to help you out with this, but I've rarely seen it being used.

    ES2015 introduces arrow functions denoted after their operator: =>. They help to cope with these situations.

    // ES5 function
    var someFunction = function(name) { ... }
    
    // ES2015 arrow function
    var someFunction = (name) => { ... } 
    

    Hence, the above could be rewritten as:

    $('.someButton').click(() => {
       // this will point to the outer scope now
    });
    

    Try it out yourself: click here

    Classes

    Against many JavaScript critics (mostly those not knowing JavaScript well enough), the language is fully object oriented. Creating a new instance of an object in ES5 is as simple as

    var aPerson = {
        firstname: 'Juri',
        surname: 'Strumpflohner',
        age: 29,
        getFullName: function() {
            return this.firstname + ' ' + this.surname;
        }
    };
    

    In this way you directly create and instantiate a new class and you can use it right away:

    aPerson.age = 30;
    console.log(aPerson.getFullName());
    

    As you can see you can set properties and invoke methods/functions on the object itself.
    There's another way of defining an object in ES5, using a so-called constructor function and then extending that one through the object's prototype.

    function Person(firstname, surname) {
      this.firstname = firstname;
      this.surname = surname;
    }
    
    Person.prototype.getFullName = function() {
        return this.firstname + ' ' + this.surname;
    }
    

    To be able to use such class, it has to be instantiated using the new keyword.

    var aPerson = new Person('Juri', 'Strumpflohner');
    aPerson.age = 30; // totally valid
    console.log(aPerson.getFullName());
    

    Most server-side developers feel a lot more comfortable with this approach. I mean, the first line of instantiating the person is completely valid C# code!
    There are long debates on the web (simply google for prototypal vs. classical inheritance) about whether this is a good thing or not. I'm not going to outline it here now. Stuff from the past.

    Class Definition

    ES2015 has native support for classes...and the discussions around them continue. But let's look at the technical details:

    class Person {
    
      constructor(firstname, surname) {
        this._firstname = firstname;
        this._surname = surname;
      }
    
      get firstname() {
        return this._firstname;
      }
    
      getFullName() {
        // woohoooo template strings!!
        return `${this._firstname} ${this._surname}`;
      }
    
    }
    
    let aPerson = new Person('Juri', 'Strumpflohner');
    console.log(aPerson.getFullName());
    console.log(aPerson.firstname);
    

    Simple, isn't it? Without telling you I used another cool feature in the getFullName() function: template strings. But more about that later.

    Another important thing is that classes are NOT hoisted, meaning that - unlike with functions - you can't invoke/instantiate a class before it is defined.

    Getters and Setters

    Classes can have getters and setters for properties which is as simple as placing get or set before the method.

    class Person {
        ...
    
        get firstname() {
            return this._firstname;
        }
        set firstname(name) {
            this._firstname = name;
        }
        …
    }
    

    Try it out yourself: click here

    Static Methods

    Static methods can be defined as follows:

    class Person {
    
        static sayHello() {
            console.log('Hi');
        }
    
    }
    

    Inheritance

    Did you ever try inheritance in ES5?? You have to make use of the prototype chain and connect the objects between them. See the code link below.

    Try it out yourself: click here

    Understanding how the prototype chain works is not that hard, but reading that code is a total mess.

    I gave it a go to rewrite the above example in ES2015.

    Try it out yourself: click here

    Not sure about you, but for me this is so much more readable. It's mostly syntactic sugar, but a lot cleaner. And note, the usage is exactly the same!

    Dynamic Method Names

    Method names can be determined at runtime as well. Just in ES5 you could call a property/function on an object using the array notation aPerson['firstname'], you can also define such methods in ES2015 classes:

    class Person {
    
      ['my' + 'DynamicName']() {
        console.log('Hi');
      }
    
    }
    
    console.log(new Person().myDynamicName());
    

    Obviously works also with static methods. An interesting use case for such dynamic or computed properties is when you use a ES2015 Symbol datatype as the name of the method. Think about it.

    Try it out yourself: click here

    Modules

    Personally, modules are the thing I like most about ES2015. So far there were different approaches to modules in browser applications.

    The easiest form, an IIFE (Immediately Invoked Function Expression):

    (function(exports, dep1, dep2) {
        var myModule = {
            doSomething: function() {
                // …
            }
        };
    
        // attach it to window or whatever
        // to make it globally available
        exports.myModule = myModule;
    
    })(window, dependency1, dependency2);
    

    The main objective: create private scope, avoid naming collisions and selectively publish functionality to the outside world. But as soon as "dependency1" and "dependency2" have to be asynchronously loaded, things get messy.

    This is where AMD and CommonJS come into play, aiming to standardise how modules are created and loaded. Addy Osmani wrote an entire online article on how to write modular JavaScript.

    So basically what you had so far was

    define('myModule', [
        'dependency1',
        'dependency2',
        ...,
        ],
        function(dep1, dep2,…) {
            // the code of my module
            var myModule = {
                doSomething: function() {
                    // use dep1, dep2,…
                }
            };
    
            return myModule;
        }
    );
    

    What suffers most: readability. The nesting level is high, even though one can somehow mitigate it with good indentation.

    The same module definition in ES2015:

    import dep1 from 'dependency1';
    import dep2 from 'dependency2';
    
    export default myModule = {
        doSomething: function() {
            // …
        }
    };
    

    Nice, isn't it?

    Very simplified, a module supports two kinds of exports:

    • default export (can only be one)
    • named export

    The shouldn't be mixed. For example:

    // mathStuff.js: named exports
    function sum(a,b) {
      return a + b;
    }
    function divide(a,b) {
      return a/b;
    }
    
    export { sum, divide as div };
    

    In this way externally, the module could be consumed with

    import { sum, div } from './mathStuff';
    

    Similarly, if we convert the above example to a default export it'll look like

    // mathStuff.js: named exports
    function sum(a,b) {
      return a + b;
    }
    function divide(a,b) {
      return a/b;
    }
    
    export default {
        sum: sum,
        div: divide
    };
    

    ..which would then be consumed as

    import mathUtils from './mathStuff';
    

    (More coming soon...)

    More is about to come. This article is written incrementally. When new updates get published, you'll get notified either over @juristr or my RSS feed.

    Create data driven applications in Qlik’s free and easy to use coding environment, brought to you in partnership with Qlik.

    Topics:
    ecmascript ,ecmascript 6 ,javascript ,traceur ,babeljs

    Published at DZone with permission of Juri Strumpflohner, DZone MVB. See the original article here.

    Opinions expressed by DZone contributors are their own.

    The best of DZone straight to your inbox.

    SEE AN EXAMPLE
    Please provide a valid email address.

    Thanks for subscribing!

    Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
    Subscribe

    {{ parent.title || parent.header.title}}

    {{ parent.tldr }}

    {{ parent.urlSource.name }}