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 :smirk:.

Right away before starting, you may also want to take a look at other good articles related to learning ES2015. A collection of good links is at the end of the article. Suggest me new ones if I don’t have them yet.

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 :wink: ).

"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 :wink:.

Try it out yourself: click here

Redefining variables 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:

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.

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 :wink:). 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.age) || 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

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 :wink:.

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';

Here are some links to other good articles around learning ES2015:

(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.

Stay tuned for more awesomeness! :smiley: