Andreas Schipplock
I'm not an assembly line worker. I'm a software developer.
I'm in favour of GNU.

JavaScript - ES 2015 / ES6 - The New Parts

by Andreas Schipplock

published on


JavaScript is dead and it successfully reincarnated in the form of ECMAScript 2015. Sometimes it's called ECMAScript 2015, ES6, ECMAScript 6 or ECMA-262 6th edition. This text doesn't cover every new addition of ECMA-262 6th edition but the most interesting ones.

If you want to read all of ECMA-262 6th edition, you can read the specification.


The first new notable addition to ES6 is 'let':

1: (function() {
2:     console.log(bar);
3:     { var bar = 'hello'; };
4:     { var bar = 'world'; };
5:     console.log(bar);
6: })();

In this example I have a function with 2 blocks inside where I define and initialize 'bar' with 'hello' and 'world'.

In this case 'bar' is defined with 'var' which tells the JS engine to scope this variable to the nearest function. The JS engine will hoist 'var bar' to the top of the function block.

I can access 'bar' just fine and it will have the value of 'world'. My 'application' will continue to run without a problem though it might cause a bug in the future. 'var'-hoisting really can lead to bugs in the future. Whenever someone adds more code, you will be prone to 'var'-hoisting-related bugs sooner or later.

The example from before but with 'let':

1: (function() {
2:     console.log(bar);
3:     { let bar = 'hello'; };
4:     { let bar = 'world'; };
5:     console.log(bar);
6: })();

In this example I have a function with 2 blocks inside where I define and initialize 'bar' with 'hello' and 'world'.

In this case 'bar'' is defined with 'let' which tells the JS engine to scope this variable only to the nearest block.

JS will not hoist 'bar' to the nearest function here and because 'let' is new I get a nice 'Uncaught ReferenceError' when I try to access this variable outside its block. This is nice because my 'application' will die when I try to access 'bar' in places where it should be inaccessible.

1: (function(){
2:     let foo = 'hello';
3:     let bar = 'world';
4:     let foo = 'hello world';
5: })();

When I define a variable with 'let' twice, I will get 'has already been declared'. This is great because I will notice when I try to use a variable with the same name in a big spaghetti code block.

01: (function() {
02:     let foo = 'hello';
03:     let bar = 'world';
05:     {
06:         let foo = 'yup, that will work';
07:         let bar = 'and this as well';
08:     };
10:     console.log(foo);
11: })();

If I want to use the same names in a new block, I can just do it because, well, it's a new block.

That's all you need to know about 'let' right now. It's much better than any automatic and 'invisible' hoisting that 'var' provides. 'let' variables can be changed anytime unlike in some other programming languages.

So 'let' isn't immutable but that's ok. Just use 'let' instead of 'var'. It will provide the expected behaviour though, after some discussions, there are some people who are actually used to the function hoisting 'var' provides. I'm personally aware that even PHP does it though I simply hate it. It's easy to miss so it's even easier to introduce new bugs especially if you aren't the only one adding code. I chose to write 'function()' only to emphasize that we are inside a function. In ES6 you, however, could write '() => {}' instead.


'const' isn't really a new addition to ES2015 because it was available many years ago in firefox but now that it's part of ES2015 other web browsers are supporting it as well. I don't have much to say about it. If you use a linter, you probably noticed the hints about 'magic numbers' already and yes, here we can just use 'const'.

Just like 'let' the scope of 'const' will be the nearest block, not the function.

1: let balance = -300;
3: console.log((function drawMoney(balance, desired) {
4:     if ((balance - desired) < -500) {
5:         return 0;
6:     }
7:     return desired;
8: })(balance, 250));

Here I have a magic number '-500' which actually has no real meaning to the person reading the source. This is bad code but easy to fix.

With a simple 'const' definition I can tell other people what I actually had in mind:

01: console.log((function drawMoney(balance, desired) {
02:     const OVERDRAFT_LIMIT = -500;
03:     const NO_MONEY = 0;
05:     if ((balance - desired) < OVERDRAFT_LIMIT) {
06:         return NO_MONEY;
07:     }
09:     return desired;
10: })(balance, 250));

Just like in any other programming language, a constant assignment cannot be re-assigned. Thus the wording:

1: const foo = 'bar';
2: foo = 'foobar';

This will throw 'Uncaught TypeError: Assignment to constant variable'. But this is limited to the assignment only.

In some programming and scripting languages you are limited to what you can assign to a constant. In ES2015 you aren't. Assign whatever you want:

1: const config = {
2:     host: 'localhost',
3:     port: 4711
4: };

However, there's a catch, because the following does work:

1: = 'some.remote.server';

If you want to force more strictness here (and it might be a useful case), you can force the objects property to be read-only:

01: const config = (() => {
02:     let properties = {};
04:     Object.defineProperty(properties, 'host', {
05:         value: 'localhost',
06:         writable: false
07:     });
09:     Object.defineProperty(properties, 'port', {
10:         value: 4711,
11:         writable: false
12:     });
14:     return properties;
15: })();

You won't be able to change ''' or 'config.port' anymore but now it looks aweful. However it's better than nothing.

'const' is a good new feature of ES2015 and I like it a lot. It's very useful, can eliminate potential bugs and will make your code a lot easier to reason about.


Default parameters in ES2015 are nice because you can create more robust implementations. Let's see an old example that would simply fail.

1: (function(cars) {
2:     cars.forEach(function(car) {
3:         console.log(car);
4:     });
5: })(['Audi', 'BMW']);

Actually this will work just fine because I provided the expected type for this function. I provided an array with two elements. It can iterate that and just print out the values.

1: (function(cars) {
2:     cars.forEach(function(car) {
3:         console.log(car);
4:     });
5: })();

Now, somehow, I forgot to provide an array and this will simply die with 'cannot read property forEach of undefined'.

1: (function(cars = []) {
2:     cars.forEach(function(car) {
3:         console.log(car);
4:     });
5: })();

In ES2015 I can provide defaults for parameters so I can assure that my function will at least run without an error.

Of course I still may check pre-conditions here and the advantage of this construct is that I know what type I have to deal with. I e.g. can call 'length' on 'cars' even though nothing was provided as an argument by the caller.

1: (function(cars = []) {
2:     if (cars.length == 0) {
3:         throw "I expected at least one car :P";
4:     }
6:     cars.forEach(function(car) {
7:         console.log(car);
8:     });
9: })();

Here's such an example where the caller doesn't provide any useful argument.

But I, as the callee, can still check on the 'length' of the array though none was passed as an argument.

1: (function drawMoney({balance, desiredAmount}) {
2:     console.log(balance);
3:     console.log(desiredAmount);
4: })({balance: -300, desiredAmount: 200});

...and because I have some history with Ada I, sometimes, like to have named 'parameters'. In JavaScript you can do whatever you want. The example above just works as expected. The caller, however, needs to know what to provide.

1: (function drawMoney({balance, desiredAmount}) {
2:     console.log(balance);
3:     console.log(desiredAmount);
4: })();

In this example the caller forgot to provide the expected 'object' as an argument. This will lead to "Uncaught TypeError: Cannot match against 'undefined' or 'null'". Your application will die.

1: (function drawMoney({balance, desiredAmount} = {}) {
2:     console.log(balance);
3:     console.log(desiredAmount);
4: })();

In ES2015 you can use defaults to prevent your application to exit unexpectedly.

However, in the above example both 'balance' and 'desiredAmount' will be 'undefined'. And I'm not so sure this is useful at all. But let's look at a possible solution.

1: (function drawMoney({balance, desiredAmount} = {balance: 0, desiredAmount: 0}) {
2:     console.log(balance);
3:     console.log(desiredAmount);
4: })();

Here I provide sane defaults which I could use to implement useful pre-conditional checks. This might be useful in such a limited use-case but in real life applications you often have to deal with quite a bit more arguments or parameters. So this approach might turn into an unreadable mess.

But in ES2015 you have the tools to create a solution for this problem as well and you probably have seen this 'pattern' in other languages as well. Let's have a look:

01: function Car(properties = {}) {
02:     let defaults = {
03:         color: 'white',
04:         ac: false,
05:         gearbox: 'auto'
06:     };
08:     let configuration = Object.assign({}, defaults, properties);
10:     return configuration;
11: }

Here I merge 'properties' with 'defaults' and let Object.assign copy it to a new Object.

This might look odd at first because it's not really a language feature that supports the handling of default arguments or parameters and yes, it's not! because a human being has to read the first few lines of the function body to know what arguments the function expects. This is by far not ideal.

I'm honest here; I liked this approach at first but after I thought about it, I tend to prefer the following solution which is baked into ES2015 and it's called destructuring assignment notation.

01: (function drawMoney({
02:     balance,
03:     desiredAmount
04: } = {
05:     balance: 0,
06:     desiredAmount: 0
07: }) {
08:     console.log(balance);
09:     console.log(desiredAmount);
10: })({balance: 1, desiredAmount: 2});

This will use '0' and '0' for 'balance' and 'desiredAmount' if you don't provide the expected object as an argument. But there's a catch! And it's an evil catch. ES2015 will try to match your arguments, so if you provide just one of the default 'argument', it won't work anymore.

01: (function drawMoney({
02:     balance,
03:     desiredAmount
04: } = {
05:     balance: 0,
06:     desiredAmount: 0
07: }) {
08:     console.log(balance);
09:     console.log(desiredAmount);
10: })({balance: 2});

I expected 'balance' to be '2' and 'desiredAmount' to be '0'. But 'balance' is now '2' and 'desiredAmount' is 'undefined'. So this approach is very dangerous. It doesn't look very good either.

So, yeah, I will stick to the 'Object.assign' approach if I have to handle more than a handful of arguments and parameters.

'defaults' are a very nice addition in ES2015. This can prevent a lot of unexpected bugs but in my case it won't save the many pre-conditional checks. Just some of them. It's a step forward though.

Rest And Spread

'rest' parameters in ES2015 can be useful but I'd personally rarely use them. simply means that you can treat as an array. But it's not an array. Technically though it is. That's it. the parameter must always be the last one.

Let me show you an example:

1: function displayCars(somethingElse, {
2:     cars.forEach(function(car) {
3:         console.log(car);
4:     });
5: }
7: displayCars('bla', 'Audi', 'BMW');
8: displayCars('bla', 'Audi', 'BMW', 'Mercedes');
9: displayCars('bla', 'Audi', 'BMW', 'Mercedes', 'Ford');

The first call will display 'Audi', 'BMW', the second 'Audi', 'BMW', 'Mercedes' and so on.

But now the confusion begins. Let me show you an example call that doesn't raise an error and can't be caught by 'try':

1: displayCars();

That will simply not print any cars. You might wonder why it won't fail because of the:

1: cars.forEach

But then again think about that cars is simply an array. But it's not. Here is an example how to call a function with the rest parameter in its function signature with an array of data:

1: let cars = ['Audi', 'BMW', 'Mercedes'];
2: displayCars('bla',;

That will finally work. The three ... are called spread operator in this case. Meeh!

let's experiment a bit:

1: displayCars('bla', cars);

This will print:

Array [ "Audi", "BMW", "Mercedes" ]

So cars as an argument simply 'destructurises':

Array [ "Audi", "BMW", "Mercedes" ]


"Audi", "BMW", "Mercedes"

So the function displayCars called like:

1: displayCars('bla',;

is actually called like:

1: displayCars('bla', 'Audi', 'BMW', 'Mercedes');

Arrow Functions

When I use callbacks in JavaScript it always feels dirty when I do something like 'var me = this;' to get access to 'this' inside the callback. And it's even prone to errors so that's the reason you can now use a better approach.

It's called 'arrow functions' and you can even shorten your 'jQuery ready' init code with it though it doesn't look too pretty if you ask me.

First I show the old variant:

01: function IP(elem) {
02:     return {
03:         prefix: 'Your IP is: ',
04:         show: function() {
05:             let me = this;
06:             $.getJSON('', function(data) {
07:                 $(elem).html(me.prefix + data.origin);
08:             });
09:         }
10:     };
11: }

Here I have to use the me = this 'hack' to achieve what I need.

Here the new variant using an arrow function:

01: function IP(elem) {
02:     return {
03:         prefix: 'Your IP is: ',
04:         show: function() {
05:             $.getJSON('', (data) => {
06:                 $(elem).html(this.prefix + data.origin);
07:             });
08:         }
09:     };
10: }

This is the 'new' variant. I use the arrow function, which is always anonymous, to stay in the same scope as before. This is a simple change but a very effective one. I just love it.

1: $(() => {
2:     let ip = new IP($('#ip'));
4: });

And I can even simplify the jQuery 'dom ready' code :).

But as always there is at least one catch I came across. I was about to believe that I can replace all function()'s with arrow functions but I was very wrong. The IP function I have still has at least one "function" notation as a value of the 'show' property and while it's technically possible to replace this function() with an arrow function...

01: function IP(elem) {
02:     return {
03:         prefix: 'Your IP is: ',
04:         show: () => {
05:             $.getJSON('', (data) => {
06:                 $(elem).html(this.prefix + data.origin);
07:             });
08:         }
09:     };
10: }

However, I will have the problem that the this context isn't what I expected. In this case this will be Window.

So how can we make it sexy again?

01: function IP(elem) {
02:     return {
03:         prefix: 'Your IP is: ',
04:         show() {
05:             $.getJSON('', (data) => {
06:                 $(elem).html(this.prefix + data.origin);
07:             });
08:         }
09:     };
10: }

With the short notation of functions inside objects I can have a short representation of what I call 'beautiful' with a 'this' which is what I expect it to be.

That's more or less all you need to know about arrow functions. Use them where you see fit.

For Of

Hurrr...for ... of loops are simple. But combined with generator functions they can turn into something more complex.

The 'old' way:

1: let makers = ['Audi', 'BMW', 'Mercedes', 'Ford'];
3: for (let index in makers) {
4:     console.log(makers[index]);
5: }

The 'new' way:

1: for (let maker of makers) {
2:     console.log(maker);
3: }

Of course you can do something like this:

1: makers.forEach((make) => {
2:     console.log(make);
3: });

...though you cannot return from this; it will loop all elements; no matter what you do :); you can use .every, or .some, of course...

Or even shorter with an arrow function:

1: makers.forEach(make => console.log(make));

Iterators With Generators

Assume the following object:

1: let myCar = {
2:     color: 'black',
3:     make: 'BMW',
4:     model: '3 Series',
5:     mileage: 89000
6: };

How would you iterate its properties? Probably like this:

1: for (let key in myCar) {
2:     console.log(myCar[key]);
3: }

You cannot use 'for .. of' here but you can convert your 'object' to be compatible with 'for .. of'.

This way you have full control on how your object is iterated. In case of the previous example you don't have any control on how the client is going to iterate your object. So you implement an iterator and control exactly how your object will react on iterations.

By implementing an iterator you also gain the ability to destructurise your object.

Here's an example:

01: let mySuperCar = {
02:     color: 'white',
03:     make: 'Mini',
04:     model: 'Cooper S',
05:     mileage: 4588,
06:     [Symbol.iterator]: function * () {
07:         let properties = Object.keys(this);
08:         for (let property of properties) {
09:             yield this[property];
10:         }
11:     }
12: };
14: for (let property of mySuperCar) {
15:     console.log(property);
16: }

That's much better. However, I had to use 'function' instead of an arrow function; you have to write 'function * ()', 'function* ()' or 'function *()'.

So with the iterator in place you cannot only iterate it with 'for..of' but you also gain the ability to destructurise your object:

1: let [ , make] = mySuperCar;
2: let [ color, , model ] = mySuperCar;
3: let [ color, make, model, mileage ] = mySuperCar;

...and so on.

But now you probably wonder if the old way to iterate an object still works on your object, right?

Well, I'm sorry but it does :)! Live with it; there is no beautiful way to hide it. You can use Object.defineProperty if you want to enforce tighter control on your properties.


ES2015 has syntactic sugar for 'classes' and you might think: 'oh great! now I can write java in JavaScript'.


01: class Car {
02:     constructor(make, model) {
03:         this.make = make;
04:         this.model = model;
05:         this.started = false;
06:     }
08:     start() {
09:         this.started = true;
10:     }
11: }
13: class BMW extends Car {
14:     constructor(model) {
15:         super('BMW', model);
16:     }
17: }
19: let bmw = new BMW('3 Series');
20: bmw.start();
21: console.log(bmw.started);

...there are still no private, public etc... for member variables. There are ways to hide information but my overall opinion on 'classes' in JavaScript is to better avoid it. I personally prefer a more functional approach to this and JavaScript fits perfectly into it.

This is all you need to know about classes in JavaScript as of now.


Maps are new in ES2015...imagine a map as a key => value store.

The following example creates a map with 3 entries:

1: let countryCodes = new Map();
2: countryCodes.set('uy', 'Uruguay');
3: countryCodes.set('de', 'Germany');
4: countryCodes.set('fr', 'France');

You can use this map like this:

1: console.log(countryCodes.get('uy'));

This will print 'Uruguay'. This is simple, right?

Of course you can iterate through 'maps':

1: for (let [countryCode, countryName] of countryCodes) {
2:     console.log(`${countryCode} => ${countryName}`);
3: }

The template string is another new feature of ES2015 which is quite handy.

You can also check if some key exists inside a map:

1: console.log(countryCodes.has('de'));
2: console.log(countryCodes.has('us'));

Displays 'true' and 'false'.

And to get rid of an entry, you can delete it:

1: if (countryCodes.has('fr')) {
2:     countryCodes.delete('fr');
3:     console.log(countryCodes.has('fr'));
4: }

This displays 'false'.


Object creation got a bit easier in ES2015.

In the past you probably wrote:

1: function Car(make, model) {
2:     return {
3:         make: make,
4:         model: model,
5:         isGoodCar: function() {
6:             return true;
7:         }
8:     };
9: }

Now you can shorten it a bit:

01: function Car(make, model) {
02:     return {
03:         make,
04:         model,
05:         isGoodCar() {
06:             return true;
07:         }
08:     };
09: }
11: let bmw = new Car('BMW', '3 series');

Destructuring And Cherrypicking

Assume the previous object:

1: let bmw = new Car('BMW', '3 series');
2: let {make} = bmw;
3: let {model, make} = bmw;

This is cherry picking from the object. The order doesn't matter but the name does.

Destructuring arrays also works:

1: let carMakers = ['Audi', 'BMW', 'Ford', 'Mercedes'];
2: let [ , b, , ] = carMakers;

'b' contains 'BMW' now.

And if you want all elements but the first:

1: let [ , ...allOther] = carMakers;

'allOther' now contains:

["BMW", "Ford", "Mercedes"]


In ES2015 you can make use of 'promises'. This feature is already available for quite some time in modern web browsers but now with ES2015 you can start to count on it.

Promises are like callbacks but with better syntax and better error handling. It's really just that.

In the following example I'm pretending to upload imaginary pictures. The 'upload' function will be asynchronous but when I call it, you will notice that it looks synchronous. As I said, it's just a better way to use callbacks; a much cleaner way if you ask me. It's easier to read.

The example code:

01: function upload(pictureName = '') {
02:     console.log('uploading: ' + pictureName);
03:     let url = '';
04:     return new Promise((resolve, reject) => {
05:         let req = new XMLHttpRequest();
06:'GET', url);
07:         req.onload = () => {
08:             if (req.status == 200) {
09:                 resolve(pictureName);
10:             } else {
11:                 reject(Error(req.statusText));
12:             }
13:         };
14:         req.onerror = () => {
15:             reject(Error('request failed for: ' + pictureName));
16:         };
17:         req.send();
18:     });
19: }

As you can see you need to create a new instance of 'Promise' and you simply have to to tell this promise when your code succeeded:

1: resolve(pictureName);

And when your code failed tell it to 'reject':

1: reject(Error(req.statusText));

And now the code that shows how to use the promise:

01: let pictures = [
02:     'picture1.jpeg',
03:     'picture2.jpeg',
04:     'picture3.jpeg'
05: ];
07: for (let picture of pictures) {
08:     upload(picture)
09:         .then((response) => {
10:             console.log('uploaded: ' + response);
11:         })
12:         .catch((error) => {
13:             console.log(error);
14:         });
15: }

And you might get the following output:

uploading: picture1.jpeg
uploading: picture2.jpeg
uploading: picture3.jpeg
uploaded: picture2.jpeg
uploaded: picture1.jpeg
uploaded: picture3.jpeg

You might have noticed that the request to upload the pictures are in order (as expected) but the execution of the uploads aren't in order anymore. This is because your code ran asynchronous.

In your implementation, though, it looks like it runs from the top to the bottom which is good as you can reason about the implementation much easier now.

This is a big improvement to the callback hell we were used to before. In practice you will create a lot of functions with 'new Promise' inside and it's not perfect as well but it's at least an improvement.

In ES2017 we will get 'async' and 'await'. But until then you can use promises.


ES2015 brought some useful set of new features to the table and these ones are what I consider worth learning.

If you want to have a complete list of all the language features, try the following link

I hope this text was useful.