ECMAScript 2015
ECMAScript 2015
Maciej Rzepiński
Buy on Leanpub

ES6-guide

ECMAScript 6 (ES6) guide

CHECK SUMMARY TO SEE TABLE OF CONTENT

I want to share with you some thoughts, snippets of code and tell you a little about the upcoming ES6. It’s my own road to know it before it will be a standard.

You might have noticed about ES6 a lot lately. This is because the standard is targeting ratification in June 2015.

See draft - ECMAScript 2015

ECMAScript 2015 is a significant update to the language. Previous (ES5) was standardized in 2009. Frameworks like AngularJS, Aurelia, ReactJS, Ionic start using it today.

ES6 includes a lot of new features:

  • arrows
  • classes
  • enhanced object literals
  • template strings
  • destructuring
  • default + rest + spread
  • let + const
  • iterators + for..of
  • generators
  • unicode
  • modules
  • module loaders
  • map + set + weakmap + weakset
  • proxies
  • symbols
  • subclassable built-ins
  • promises
  • math + number + string + object APIs
  • binary and octal literals
  • reflect api
  • tail calls

I will try to describe each of these in the next stories, so stay updated.

Thanks to the use of transpilers (Babel, Traceur and others) we can actually use it right now until browsers fully catch up.

Browser support matrix

ES6 Repl in Chrome Devl Tools - Scratch JS

1 Future is bright.

People from the community denounced these words. I have only one to add: we can handle it easily!

let + const

First topic about ECMAScript 2015 is let + const. If you are familiar with JavaScript, you have probably known the term: scope. If you are not that lucky, don’t worry about it. I’ll explain that in a few words below.

Why I mentioned something about JavaScript scope? This is because let and const have a very strong connection with that word. Firstly, imagine and old way (and still valid) to declare a new variable in your JS code using ES5:

 1 // ES5
 2 
 3 var a = 1;
 4 
 5 if (1 === a) {
 6   var b = 2;
 7 }
 8 
 9 for (var c = 0; c < 3; c++) {
10   // …
11 }
12 
13 function letsDeclareAnotherOne() {
14   var d = 4;
15 }
16 
17 console.log(a); // 1
18 console.log(b); // 2
19 console.log(c); // 3
20 console.log(d); // ReferenceError: d is not defined
21 
22 // window
23 console.log(window.a); // 1
24 console.log(window.b); // 2
25 console.log(window.c); // 3
26 console.log(window.d); // undefined
  1. We can see that variable a is declared as global. Nothing surprising.
  2. Variable b is inside an if block, but in JavaScript it doesn’t create a new scope. If you are familiar with other languages, you can be disappointed, but this is JavaScript and it works as you see.
  3. The next statement is a for loop. C variable is declared in this for loop, but also in the global scope.
  4. Until variable d is declared in his own scope. It’s inside a function and only function creates new scopes.

Variables in JavaScript are hoisted to the top!

Hoisting is JavaScript’s default behavior of moving all declarations to the top of the current scope (to the top of the current script or the current function).

In JavaScript, a variable can be declared after it has been used. In other words - a variable can be used before it has been declared!

One more rule, more aware: JavaScript only hoists declarations, not initialization.

 1 // scope and variable hoisting
 2 
 3 var n = 1;
 4 
 5 (function () {
 6   console.log(n);
 7   var n = 2;
 8   console.log(n);
 9 })();
10 
11 console.log(n);

Let’s look for the new keywords in JavaScript ECMAScript 2015: let and const.

let

We can imagine that let is a new var statement. What is the difference? let is block scoped. Let’s see an example:

 1 // ES6 — let
 2 
 3 let a = 1;
 4 
 5 if (1 === a) {
 6   let b = 2;
 7 }
 8 
 9 for (let c = 0; c < 3; c++) {
10   // …
11 }
12 
13 function letsDeclareAnotherOne() {
14   let d = 4;
15 }
16 
17 console.log(a); // 1
18 console.log(b); // ReferenceError: b is not defined
19 console.log(c); // ReferenceError: c is not defined
20 console.log(d); // ReferenceError: d is not defined
21 
22 // window
23 console.log(window.a); // 1
24 console.log(window.b); // undefined
25 console.log(window.c); // undefined
26 console.log(window.d); // undefined

As we can see, this time only variable a is declared as a global. let gives us a way to declare block scoped variables, which is undefined outside it.

I use Chrome (stable version) with #enable-javascript-harmony flag enabled. Visit chrome://flags/#enable-javascript-harmony, enable this flag, restart Chrome and you will get many new features.

You can also use BabelJS repl or Traceur repl and compare results.

const

const is single-assignment and like a let, block-scoped declaration.

1 // ES6 const
2 
3 {
4   const PI = 3.141593;
5   PI = 3.14; // throws “PI” is read-only
6 }
7 
8 console.log(PI); // throws ReferenceError: PI is not defined

const cannot be reinitialized. It will throw an Error when we try to assign another value.

Let’s look for the equivalent in ES5:

1 // ES5 const
2 
3 var PI = (function () {
4   var PI = 3.141593;
5   return function () { return PI; };
6 })();

arrow functions

A new syntactic sugar which ES6 brings us soon, called arrow functions (also known as a fat arrow function). It’s a shorter syntax compared to function expressions and lexically binds this value.

REMEMBER - Arrow functions are always anonymous.

Syntactic sugar

How does it look? It’s a signature:

1 ([param] [, param]) => {
2  statements
3 }
4 
5            param => expression
6 (param1, param2) => { block }

..and it could be translated to:

1     () => {  }       // no argument
2      x => {  }       // one argument
3 (x, y) => {  }       // several arguments
4 
5 x => { return x * x } // block
6 x => x * x            // expression, same as above

Lambda expressions in JavaScript! Cool!

Instead of writing:

1 [3, 4, 5].map(function (n) {
2   return n * n;
3 });

..you can write something like this:

1 [3, 4, 5].map(n => n * n);

Awesome. Isn’t it? There is more!

Fixed “this” = lexical “this”

The value of this inside of the function is determined by where the arrow function is defined not where it is used.

No more bind, call and apply! No more:

1 var self = this;

It solves a major pain point (from my point of view) and has the added bonus of improving performance through JavaScript engine optimizations.

 1 // ES5
 2 function FancyObject() {
 3  var self = this;
 4 
 5  self.name = 'FancyObject';
 6  setTimeout(function () {
 7   self.name = 'Hello World!';
 8  }, 1000);
 9 }
10 
11 // ES6
12 function FancyObject() {
13   this.name = 'FancyObject';
14   setTimeout(() => {
15     this.name = 'Hello World!'; // properly refers to FancyObject
16   }, 1000);
17 }

The same function

  • typeof returns function
  • instanceof returns Function

Limitations

  • It’s cannot be used as a constructor and will throw an error when used with new.
  • Fixed this means that you cannot change the value of this inside of the function. It remains the same value throughout the entire lifecycle of the function.
  • Regular functions can be named.
  • Functions declarations are hoisted (can be used before they are declared).

default + rest + spread

ECMAScript 2015 functions made a significant progress, taking into account years of complaints. The result is a number of improvements that make programming in JavaScript less error-prone and more powerful.

Let’s see three new features which give us extended parameter handling.

default

It’s a simple, little addition that makes it much easier to handle function parameters. Functions in JavaScript allow any number of parameters to be passed regardless of the number of declared parameters in the function definition. You probably know commonly seen pattern in current JavaScript code:

1 function inc(number, increment) {
2   // set default to 1 if increment not passed
3   // (or passed as undefined)
4   increment = increment || 1;
5   return number + increment;
6 }
7 
8 console.log(inc(2, 2)); // 4
9 console.log(inc(2));    // 3

The logical OR operator (||) always returns the second operand when the first is falsy.

ES6 gives us a way to set default function parameters. Any parameters with a default value are considered to be optional.

ES6 version of inc function looks like this:

1 function inc(number, increment = 1) {
2   return number + increment;
3 }
4 
5 console.log(inc(2, 2)); // 4
6 console.log(inc(2));    // 3

You can also set default values to parameters that appear before arguments without default values:

1 function sum(a, b = 2, c) {
2   return a + b + c;
3 }
4 
5 console.log(sum(1, 5, 10));         // 16 -> b === 5
6 console.log(sum(1, undefined, 10)); // 13 -> b as default

You can even execute a function to set default parameter. It’s not restricted to primitive values.

 1 function getDefaultIncrement() {
 2   return 1;
 3 }
 4 
 5 function inc(number, increment = getDefaultIncrement()) {
 6   return number + increment;
 7 }
 8 
 9 console.log(inc(2, 2)); // 4
10 console.log(inc(2));    // 3

rest

Let’s rewrite sum function to handle all arguments passed to it (without validation - just to be clear). If we want to use ES5, we probably also want to use arguments object.

 1 function sum() {
 2    var numbers = Array.prototype.slice.call(arguments),
 3        result = 0;
 4    numbers.forEach(function (number) {
 5        result += number;
 6    });
 7    return result;
 8 }
 9 
10 console.log(sum(1));             // 1
11 console.log(sum(1, 2, 3, 4, 5)); // 15

But it’s not obvious that the function is capable of handling any parameters. W have to scan body of the function and find arguments object.

ECMAScript 6 introduces rest parameters to help us with this and other pitfalls.

arguments - contains all parameters including named parameters

Rest parameters are indicated by three dots preceding a parameter. Named parameter becomes an array which contain the rest of the parameters.

sum function can be rewritten using ES6 syntax:

 1 function sum(numbers) {
 2   var result = 0;
 3   numbers.forEach(function (number) {
 4     result += number;
 5   });
 6   return result;
 7 }
 8 
 9 console.log(sum(1)); // 1
10 console.log(sum(1, 2, 3, 4, 5)); // 15

Restriction: no other named arguments can follow in the function declaration.

1 function sum(numbers, last) { // causes a syntax error
2   var result = 0;
3   numbers.forEach(function (number) {
4     result += number;
5   });
6   return result;
7 }

spread

The spread is closely related to rest parameters, because of (three dots) notation. It allows to split an array to single arguments which are passed to the function as separate arguments.

Let’s define our sum function an pass spread to it:

1 function sum(a, b, c) {
2   return a + b + c;
3 }
4 
5 var args = [1, 2, 3];
6 console.log(sum(args)); // 6

ES5 equivalent is:

1 function sum(a, b, c) {
2   return a + b + c;
3 }
4 
5 var args = [1, 2, 3];
6 console.log(sum.apply(undefined, args)); // 6

Instead using an apply function, we can just type …args and pass all array argument separately.

We can also mix standard arguments with spread operator:

1 function sum(a, b, c) {
2   return a + b + c;
3 }
4 
5 var args = [1, 2];
6 console.log(sum(args, 3)); // 6

The result is the same. First two arguments are from args array, and the last passed argument is 3.

destructuring

Destructuring is one more little addition to the upcoming JavaScript standard, which helps us write code more flexibly and effectively.

It allows binding using pattern matching. We can use it for matching arrays and objects. It’s similar to standard object look up and returns undefined when value is not found.

arrays

Today it’s common to see the code such as this.

1 // ES5
2 var point = [1, 2];
3 var xVal = point[0],
4     yVal = point[1];
5 
6 console.log(xVal); // 1
7 console.log(yVal); // 2

ES6 gives us destructuring of arrays into individual variables during assignment which is intuitive and flexible.

 1 // ES6
 2 let point = [1, 2];
 3 let [xVal, yVal] = point;
 4 
 5 console.log(xVal); // 1
 6 console.log(yVal); // 2
 7 // .. and reverse!
 8 [xVal, yVal] = [yVal, xVal];
 9 
10 console.log(xVal); // 2
11 console.log(yVal); // 1

We can even omit some values..

1 let threeD = [1, 2, 3];
2 let [a, , c] = threeD;
3 
4 console.log(a); // 1
5 console.log(c); // 3

..and have nested array destructuring.

1 let nested = [1, [2, 3], 4];
2 let [a, [b], d] = nested;
3 
4 console.log(a); // 1
5 console.log(b); // 2
6 console.log(d); // 4

objects

As well as the array syntax, ES6 also has the ability to destructure objects. It uses an object literal on the left side of an assignment operation. Object pattern is very similar to array pattern seen above. Let’s see:

1 let point = {
2   x: 1,
3   y: 2
4 };
5 let { x: a, y: b } = point;
6 
7 console.log(a); // 1
8 console.log(b); // 2

It supports nested object as well as array pattern.

 1 let point = {
 2   x: 1,
 3   y: 2,
 4   z: {
 5     one: 3,
 6     two: 4
 7   }
 8 };
 9 let { x: a, y: b, z: { one: c, two: d } } = point;
10 
11 console.log(a); // 1
12 console.log(b); // 2
13 console.log(c); // 3
14 console.log(d); // 4

mixed

We can also mix objects and arrays together and use theirs literals.

 1 let mixed = {
 2   one: 1,
 3   two: 2,
 4   values: [3, 4, 5]
 5 };
 6 let { one: a, two: b, values: [c, , e] } = mixed;
 7 
 8 console.log(a); // 1
 9 console.log(b); // 2
10 console.log(c); // 3
11 console.log(e); // 5

But I think the most interesting is that we are able to use functions which return destructuring assignment.

 1 function mixed () {
 2   return {
 3     one: 1,
 4     two: 2,
 5     values: [3, 4, 5]
 6   };
 7 }
 8 let { one: a, two: b, values: [c, , e] } = mixed();
 9 
10 console.log(a); // 1
11 console.log(b); // 2
12 console.log(c); // 3
13 console.log(e); // 5

The same result! It gives us a lot of possibilities to use it in our code.

attention!

If the value of a destructuring assignment isn’t match, it evaluates to undefined.

1 let point = {
2   x: 1
3 };
4 let { x: a, y: b } = point;
5 
6 console.log(a); // 1
7 console.log(b); // undefined

If we try to omit var, let or const, it will throw an error, because block code can’t be destructuring assignment.

1 let point = {
2   x: 1
3 };
4 
5 { x: a } = point; // throws error

We have to wrap it in parentheses. Just that ☺

1 let point = {
2   x: 1
3 };
4 ({ x: a } = point);
5 
6 console.log(a); // 1

strings

I gonna show you a couple of changes to strings in JavaScript, which will be available when ES6 comes. A syntactic sugar, which could be helpful in daily work.

template strings

First, a string interpolation. Yep, template strings (finally) support string interpolation. ES6 brings us also support for multi-line syntax and raw literals.

1 let x = 1;
2 let y = 2;
3 let sumTpl = `${x} + ${y} = ${x + y}`;
4 
5 console.log(sumTpl); // 1 + 2 = 3

As you can see, we can inject values to string by using ${value} syntax. Another thing to consider is grave accent - a char under the tilde (~) on a keyboard. A template literal string must be wrapped by it, to work properly.

The example above is an equivalent (in ES5) to simply (Babel version):

1 var x = 1;
2 var y = 2;
3 var sumTpl = "" + x + " + " + y + " = " + (x + y);
4 
5 console.log(sumTpl); // 1 + 2 = 3

This feature is very useful and almost removes the need for a template system.

Template strings provide also multi-line syntax, which is not legal in ES5 and earlier.

1 let types = `Number
2 String
3 Array
4 Object`;
5 console.log(types); // Number
6                     // String
7                     // Array
8                     // Object

ES5 equivalent:

1 var types = "Number\nString\nArray\nObject";
2 console.log(types); // Number
3                     // String
4                     // Array
5                     // Object

The last thing is access the raw template string content where backslashes are not interpreted. We don’t have equivalent in ES5 here.

1 let interpreted = 'raw\nstring';
2 let esaped = 'raw\\nstring';
3 let raw = String.raw`raw\nstring`;
4 
5 console.log(interpreted);    // raw
6                              // string
7 console.log(raw === esaped); // true

extended support for Unicode

ES6 gives us full support for Unicode within strings and regular expressions. It’s non-breaking addition allows to building global apps.

Let’s see an example:

1 let str = '𠮷';
2 
3 console.log(str.length);             // 2
4 console.log(str === '\uD842\uDFB7'); // true

You can see that character 𠮷* represented by two 16-bit code units. It’s a surrogate pair in which we have a single code point represented by two code units. The length of that string is also 2.

Surrogate pairs are used in UTF-16 to represent code points above U+FFFF.

1 console.log(str.charCodeAt(0)); // 55362
2 console.log(str.charCodeAt(1)); // 57271

The charCodeAt() method returns the 16-bit number for each code unit.

ES6 allows encoding of strings in UTF-16. JavaScript can now support work with surrogate pairs. It gives us also a new method codePointAt() that returns Unicode code point instead of Unicode code unit.

1 console.log(str.codePointAt(0)); // 134071
2 console.log(str.codePointAt(1)); // 57271
3 console.log(str.codePointAt(0) === 0x20BB7); // true

It works the same as charCodeAt() except for non-BMP characters.

BMP - Basic Multilingual Plane - the first 2^16 code points.

codePointAt() returns full code point at the 0 position. codePointAt() and charCodeAt() return the same value for position 1.

We can also do a reverse operation with another new method added to ES6: fromCodePoint().

1 console.log(String.fromCodePoint(134071));  // "𠮷"
2 console.log(String.fromCodePoint(0x20BB7)); // "𠮷"

Unicode code unit escape sequences consist of six characters, namely \u plus four hexadecimal digits, and contribute one code unit.

Unicode code point escape sequences consist of five to ten characters, namely \u{ 1–6 hexadecimal digits }, and contribute one or two code units.

Dealing with that two definitions, above example could be represented by one code point in ES6:

1 // ES6
2 console.log('\u{20BB7}'); // 𠮷
3 console.log('\u{20BB7}' === '\uD842\uDFB7'); // true
4 
5 // ES5
6 console.log('\u20BB7); // 7!
7 console.log('\u20BB7' === '\uD842\uDFB7'); // false

In ES5 we get an unexpected result when we try to match one single character using regular expression.

1 console.log(/^.$/.test(str)); // false - length is 2

ES6 allows us to use new RegExp u mode to handle code points. It is simply a new u flag (u == Unicode).

1 console.log(/^.$/u.test(str)); // true

Adding u flag allows to correctly match the string by characters instead of code units.

strings are iterable

Strings are iterable by using the for-of loop which I will cover in more detail in iterators article later. I write about it now because it enumerate Unicode code points and each may comprise one or two characters.

1 let str = 'abc\uD842\uDFB7';
2 console.log(str.length); // 5
3 for (let c of str) {
4   console.log(c); // a
5                   // b
6                   // c
7                   // 𠮷
8 }

We can also use spread operator to transform string into an array with full Unicode support.

1 let str = 'abc\uD842\uDFB7';
2 let chars = [str];
3 console.log(chars); // ['a', 'b', 'c', '𠮷']

new string methods

repeat(n) - string repeats by n times

1 console.log('abc|'.repeat(3)); // 'abc|abc|abc|'

startsWith(str, starts = 0) : boolean - check if string starts with str, starting from starts

1 console.log('ecmascript'.startsWith('ecma'));      // true
2 console.log('ecmascript'.startsWith('script', 4)); // true

endsWith(str, ends = str.length) : boolean - check if string ends with str, ends - where the string to be checked ends

1 console.log('ecmascript'.endsWith('script'));  // true
2 console.log('ecmascript'.endsWith('ecma', 4)); // true

includes(str, starts = 0) : boolean - check if string contain str, starting from starts

1 console.log('ecmascript'.includes('ecma'));      // true
2 console.log('ecmascript'.includes('script', 4)); // true

iterators

iterator & iterable

An iterator is an object with a next method that returns { done, value } tuples.

ES6 gives us a pattern for creating custom iterators and it has a similar implementation to Java Iterable or .NET IEnumerable. It has also built-in iterables: String, Array, TypedArray, Map and Set. An iterator object can be any object with a next() method.

Iterable is an object which has Symbol.iterator method inside.

Symbol is in turn an unique and immutable data type which can be used as an identifier for object properties — no equivalent in ES5.

 1 // Symbol
 2 let s1 = Symbol('abc');
 3 let s2 = Symbol('abc');
 4 
 5 console.log(s1 !== s2); // true
 6 console.log(typeof s1); // 'symbol'
 7 
 8 let obj = {};
 9 obj[s1] = 'abc';
10 console.log(obj); // Object { Symbol(abc): 'abc' }

Let’s see a simple iterator written from scratch, which allows us to iterate through random numbers which are dynamically generated by next() method. A function returning iterable object take one argument (items) which is used to determine if the iterator should stop and returns done = true.

 1 let random1_10 = function (items = 1) {
 2   return {
 3     [Symbol.iterator]() {
 4       let cur = 0;
 5       return {
 6         next() {
 7           let done = cur === items,
 8               random = Math.floor(Math.random() * 10) + 1;
 9           ++cur;
10           return {
11             done: done,
12             value: random
13           }
14         }
15       }
16     }
17   };
18 };
19 
20 for (let n of random1_10(5)) {
21   console.log(n); // prints 5 random numbers
22 }

Every time for-of loop call next() method, an iterator generate a random number and returns it to the loop.

If the iterator returns done = true, you can omit value, so the result will be { done: true }

 1 let random1_10 = function (items = 1) {
 2   return {
 3     [Symbol.iterator]() {
 4       let cur = 0;
 5       return {
 6         next() {
 7           if (cur === items) {
 8             return {
 9               done: true
10             }
11           }
12           ++cur;
13           return {
14             done: false,
15             value: Math.floor(Math.random() * 10) + 1
16           }
17         }
18       }
19     }
20   };
21 };
22 
23 for (let n of random1_10(5)) {
24  console.log(n); // prints 5 random numbers
25 }

for-of loop

ES6 has a new loop — for-of. It works with iterables. Let’s look at his signature:

1 for (LET of ITERABLE) {
2   CODE BLOCK
3 }

It’s similar to for-in loop, which can be used to iterate through object properties (plain old Objects).

Arrays in ES6 are iterable by default, so we finally can use for-of for looping over the elements.

1 const arr = [1, 2, 3, 4, 5];
2 
3 for (let item of arr) {
4   console.log(item); // 1
5                      // 2
6                      // 3
7                      // 4
8                      // 5
9 }

Inside the for-of loop, we can even use a break, continue and return.

 1 const arr = [1, 2, 3, 4, 5];
 2 
 3 for (let item of arr) {
 4   if (item > 4) {
 5     break;
 6   }
 7   if (0 !== item % 2) {
 8     continue;
 9   }
10   console.log(item); // 2
11                      // 4
12 }

generators

Generators are simply subtypes of Iterators which I wrote about previously. They are a special kind of function that can be suspended and resumed, which is making a difference to iterators. Generators use function* and yield operators to work their magic.

The yield operator returns a value from the function and when the generator is resumed, execution continues after the yield.

We also have to use function* (with star character) instead of a function to return a generator instance.

!!! Generators have been borrowed from Python language.

The most magical feature in ES6!

Why? Take a look:

 1 function* generator () {
 2   yield 1;
 3   // pause
 4   yield 2;
 5   // pause
 6   yield 3;
 7   // pause
 8   yield 'done?';
 9   // done
10 }
11 let gen = generator(); // [object Generator]
12 
13 console.log(gen.next()); // Object {value: 1, done: false}
14 console.log(gen.next()); // Object {value: 2, done: false}
15 console.log(gen.next()); // Object {value: 3, done: false}
16 console.log(gen.next()); // Object {value: 'done?', done: false}
17 console.log(gen.next()); // Object {value: undefined, done: true}
18 console.log(gen.next()); // Object {value: undefined, done: true}
19 
20 for (let val of generator()) {
21   console.log(val); // 1
22                     // 2
23                     // 3
24                     // 'done?'
25 }

As you can see, the generator has four yield statements. Each returns a value, pauses execution and moves to the next yield when next() method is called. Calling a function produces an object for controlling generator execution, a so-called generator object.

Use is similar to iterators. We have next() method as I mentioned above and we can even use it with for-of loop.

Below is an example of a generator called random1_10, which returns random numbers from 1 to 10.

 1 function* random1_10 () {
 2   while (true) {
 3     yield Math.floor(Math.random() * 10) + 1;
 4   }
 5 }
 6 
 7 let rand = random1_10();
 8 console.log(rand.next());
 9 console.log(rand.next());
10 // …

Generator has never ending while loop. It produces random numbers every time when you call next() method.

ES5 implementation:

 1 function random1_10 () {
 2   return {
 3     next: function() {
 4       return {
 5         value: Math.floor(Math.random() * 10) + 1,
 6         done: false
 7       };
 8     }
 9   };
10 }
11 
12 let rand = random1_10();
13 console.log(rand.next());
14 console.log(rand.next());
15 // …

We can also mix generators together:

 1 function* random (max) {
 2   yield Math.floor(Math.random() * max) + 1;
 3 }
 4 
 5 function* random1_20 () {
 6   while (true) {
 7     yield* random(20);
 8   }
 9 }
10 
11 let rand = random1_20();
12 console.log(rand.next());
13 console.log(rand.next());
14 // …

random1_20 generator returns random values from 1 to 20. It uses random generator inside to create random number each time when yield statement is reached.

classes and inheritance

OO keywords is probably the most awaited features in ES6. Classes are something like another syntactic sugar over the prototype-based OO pattern. We now have one, concise way to make class patterns easier to use.

Over the prototype-based OO pattern to ensure backwards compatibility.

Overview — an example of ES6 class syntax and ES5 equivalent

 1 class Vehicle {
 2 
 3   constructor (name, type) {
 4     this.name = name;
 5     this.type = type;
 6   }
 7 
 8   getName () {
 9     return this.name;
10   }
11 
12   getType () {
13     return this.type;
14   }
15 
16 }
17 
18 let car = new Vehicle('Tesla', 'car');
19 console.log(car.getName()); // Tesla
20 console.log(car.getType()); // car

It’s naive example, but we can see a new keywords as class and constructor.

ES5 equivalent could be something like this:

 1 function Vehicle (name, type) {
 2   this.name = name;
 3   this.type = type;
 4 };
 5 
 6 Vehicle.prototype.getName = function getName () {
 7   return this.name;
 8 };
 9 
10 Vehicle.prototype.getType = function getType () {
11   return this.type;
12 };
13 
14 var car = new Vehicle('Tesla', 'car');
15 console.log(car.getName()); // Tesla
16 console.log(car.getType()); // car

Classes support prototype-based inheritance, super calls, instance and static methods and constructors.

It’s simple. We instantiate our classes the same way, but let’s add some..

inheritance

..to it and start from ES5 example:

 1 function Vehicle (name, type) {
 2   this.name = name;
 3   this.type = type;
 4 };
 5 
 6 Vehicle.prototype.getName = function getName () {
 7   return this.name;
 8 };
 9 
10 Vehicle.prototype.getType = function getType () {
11   return this.type;
12 };
13 
14 function Car (name) {
15   Vehicle.call(this, name, 'car');
16 }
17 
18 Car.prototype = Object.create(Vehicle.prototype);
19 Car.prototype.constructor = Car;
20 Car.parent = Vehicle.prototype;
21 Car.prototype.getName = function () {
22   return 'It is a car: '+ this.name;
23 };
24 
25 var car = new Car('Tesla');
26 console.log(car.getName()); // It is a car: Tesla
27 console.log(car.getType()); // car

And now look at the ES6 version:

 1 class Vehicle {
 2 
 3   constructor (name, type) {
 4     this.name = name;
 5     this.type = type;
 6   }
 7 
 8   getName () {
 9     return this.name;
10   }
11 
12   getType () {
13     return this.type;
14   }
15 
16 }
17 
18 class Car extends Vehicle {
19 
20   constructor (name) {
21     super(name, 'car');
22   }
23 
24   getName () {
25     return 'It is a car: ' + super.getName();
26   }
27 
28 }
29 
30 let car = new Car('Tesla');
31 console.log(car.getName()); // It is a car: Tesla
32 console.log(car.getType()); // car

We see how easy is to implement inheritance with ES6. It’s finally looking like in other OO programming languages. We use extends to inherit from another class and the super keyword to call the parent class (function). Moreover, getName() method was overridden in subclass Car.

super — previously to achieve such functionality in Javascript required the use of call or apply

static

 1 class Vehicle {
 2 
 3   constructor (name, type) {
 4     this.name = name;
 5     this.type = type;
 6   }
 7 
 8   getName () {
 9     return this.name;
10   }
11 
12   getType () {
13     return this.type;
14   }
15 
16   static create (name, type) {
17     return new Vehicle(name, type);
18   }
19 
20 }
21 
22 let car = Vehicle.create('Tesla', 'car');
23 console.log(car.getName()); // Tesla
24 console.log(car.getType()); // car

Classes give us an opportunity to create static members. We don’t have to use the new keyword later to instantiate a class.

static methods (properties) are also inherited and could be called by super

get / set

Other great things in upcoming ES6 are getters and setters for object properties. They allow us to run the code on the reading or writing of a property.

 1 class Car {
 2 
 3   constructor (name) {
 4     this._name = name;
 5   }
 6 
 7   set name (name) {
 8     this._name = name;
 9   }
10 
11   get name () {
12     return this._name;
13   }
14 
15 }
16 
17 let car = new Car('Tesla');
18 console.log(car.name); // Tesla
19 
20 car.name = 'BMW';
21 console.log(car.name); // BMW

I use ‘_’ prefix to create a (tmp) field to store name property.

Enhanced Object Properties

The last thing I have to mention is property shorthand, computed property names and method properties.

ES6 gives us shorter syntax for common object property definition:

 1 // ES6
 2 let x = 1,
 3     y = 2,
 4     obj = { x, y };
 5 
 6 console.log(obj); // Object { x: 1, y: 2 }
 7 
 8 // ES5
 9 var x = 1,
10     y = 2,
11     obj = {
12       x: x,
13       y: y
14     };
15 
16 console.log(obj); // Object { x: 1, y: 2 }

As you can see, this works because the property value has the same name as the property identifier.

Another thing is ES6 support for computed names in object property definitions:

 1 // ES6
 2 let getKey = () => '123',
 3     obj = {
 4       foo: 'bar',
 5       ['key_' + getKey()]: 123
 6     };
 7 
 8 console.log(obj); // Object { foo: 'bar', key_123: 123 }
 9 
10 // ES5
11 var getKey = function () {
12       return '123';
13     },
14     obj = {
15       foo: 'bar'
16     };
17 obj['key_' + getKey()] = 123;
18 
19 console.log(obj); // Object { foo: 'bar', key_123: 123 }

The one last thing is method properties seen in classes above. We can even use it in object definitions:

 1 // ES6
 2 let obj = {
 3   name: 'object name',
 4   toString () { // 'function' keyword is omitted here
 5     return this.name;
 6   }
 7 };
 8 
 9 
10 console.log(obj.toString()); // object name
11 
12 // ES5
13 var obj = {
14   name: 'object name',
15   toString: function () {
16     return this.name;
17   }
18 };
19 
20 console.log(obj.toString()); // object name

modules

Today we have a couple of ways to create modules, export & import them. JavaScript doesn’t have any built-in module loader yet. Upcoming ECMAScript 2015 standard gives us a reason to make people happy. Finally ;)

We have third party standards: CommonJS and AMD. The most popular, but, unfortunately, incompatible standards for module loaders.

CommonJS is known from Node.js. It’s mostly dedicated for servers and it supports synchronous loading. It also has a compact syntax focused on export and require keywords.

AMD and the most popular implementation - RequireJS are dedicated for browsers. AMD supports asynchronous loading, but has more complicated syntax than CommonJS.

The goal for ES6 is (was) to mix these two standards and make both user groups happy.

ES6 gives us an easy syntax and support for asynchronous and configurable module loading.

Async model — no code executes until requested modules are available and processed.

Named export

Modules can export multiple objects, which could be simple variables or functions.

1 export function multiply (x, y) {
2   return x * y;
3 };

We can also export a function stored in a variable, but we have to wrap the variable in a set of curly braces.

1 var multiply = function (x, y) {
2   return x * y;
3 };
4 
5 export { multiply };

We can even export many objects and like in the above example — we have to wrap exported statements in a set of curly braces if we use one export keyword.

 1 export hello = 'Hello World';
 2 export function multiply (x, y) {
 3   return x * y;
 4 };
 5 
 6 // === OR ===
 7 
 8 var hello = 'Hello World',
 9     multiply = function (x, y) {
10       return x * y;
11     };
12 
13 export { hello, multiply };

Let’s just imagine that we have modules.js file with all exported statements. To import them in another file (in the same directory) we use … import { .. } from .. syntax:

1 import { hello } from 'modules';

We can omit .js extension just like in CommonJS and AMD.

We can even import many statements:

1 import { hello, multiply } from 'modules';

Imports may also be aliased:

1 import { multiply as pow2 } from 'modules';

..and use wildcard (*) to import all exported statemets:

1 import * from 'modules';

Default export

In our module, we can have many named exports, but we can also have a default export. It’s because our module could be a large library and with default export we can import then an entire module. It could be also useful when our module has single value or model (class / constructor).

One default export per module.

1 export default function (x, y) {
2   return x * y;
3 };

This time we don’t have to use curly braces for importing and we have a chance to name imported statement as we wish.

1 import multiply from 'modules';
2 
3 // === OR ===
4 
5 import pow2 from 'modules';
6 
7 // === OR ===
8 
9 ...

Module can have both named exports and a default export.

1 // modules.js
2 export hello = 'Hello World';
3 export default function (x, y) {
4   return x * y;
5 };
6 
7 // app.js
8 import pow2, { hello } from 'modules';

The default export is just a named export with the special name default.

1 // modules.js
2 export default function (x, y) {
3   return x * y;
4 };
5 
6 // app.js
7 import { default } from 'modules';

API

In addition, there is also a programmatic API and it allows to:

  • Programmatically work with modules and scripts
  • Configure module loading

SystemJS — universal dynamic module loader — loads ES6 modules, AMD, CommonJS and global scripts in the browser and NodeJS. Works with both Traceur and Babel.

Module loader should support: * Dynamic loading

  • Global namespace isolation
  • Nested virtualization
  • Compilation hooks

promises

Promises aren’t a new and shiny idea. I use it every day in my AngularJS code. It’s based on kriskowal / q library:

A tool for creating and composing asynchronous promises in JavaScript.

It’s a library for asynchronous programming, to make our life easier. But, before I describe promises, I have to write something about callbacks.

Callbacks and callback hell

Until I remember, JavaScript coders use callbacks for all browser-based asynchronous functions (setTimeout, XMLHttpRequest, etc.).

Look at naive example:

 1 console.log('start!');
 2 setTimeout(function () {
 3   console.log('ping');
 4   setTimeout(function () {
 5     console.log('pong');
 6     setTimeout(function () {
 7       console.log('end!');
 8     }, 1000);
 9   }, 1000);
10 }, 1000);
11 
12 // start!
13 // after 1 sec: ping
14 // .. 1 sec later: pong
15 // .. and: end!

We have simple code which prints some statements to the console. I used a setTimeout function here, to show callback functions passed to invoke later (1 sec here). It looks terrible and we have only 3 steps here. Let’s imagine more steps. It will look like you build a pyramid, not nice, readable code. Awful, right? It’s called callback hell and we have it everywhere.

Callback Hell

Promises

Support for promises is a very nice addition to the language. It’s finally native in the ES6.

Promises are a first class representation of a value that may be made available in the future.

A promise can be:

  • fulfilled - promise succeeded
  • rejected - promise failed
  • pending - not fulfilled or not rejected yet
  • settled - fulfilled or rejected

Every returned promise object also has a then method to execute code when a promise is settled.

Yep, promise object, because..

callbacks are functions, promises are objects.

Callbacks are blocks of code to execute in response to.. something (event). Promises are objects which store an information about the state.

How does it look like? Let’s see:

1 new Promise((resolve, reject) => {
2   // when success, resolve
3   let value = 'success';
4   resolve(value);
5 
6   // when an error occurred, reject
7   reject(new Error('Something happened!'));
8 });

Promise calls its resolve function when it’s fulfilled (success) and reject function otherwise (failure).

Promises are objects, so it’s not passed as arguments like callbacks, it’s returned. The return statement is an object which is a placeholder for the result, which will be available in the future.

Promises have just one responsibility-they represent only one event. Callbacks can handle multiple events, many times.

We can assign returned value (object) to the let statement:

1 let promise = new Promise((resolve, reject) => {
2   // when success, resolve
3   let value = 'success';
4   resolve(value);
5 
6   // when an error occurred, reject
7   reject(new Error('Something happened!'));
8 });

As I mentioned above-promise object also has a then method to execute code when the promise is settled.

1 promise.then(onResolve, onReject)

We can use this function to handle onResolve and onReject values returned by a promise. We can handle success, failure or both.

 1 let promise = new Promise((resolve, reject) => {
 2   // when success, resolve
 3   let value = 'success';
 4   resolve(value);
 5 
 6   // when an error occurred, reject
 7   reject(new Error('Something happened!'));
 8 });
 9 
10 promise.then(response => {
11   console.log(response);
12 }, error => {
13   console.log(error);
14 });
15 // success

Our code above never executes reject function, so we can omit it for simplicity:

1 let promise = new Promise(resolve => {
2   let value = 'success';
3   resolve(value);
4 });
5 
6 promise.then(response => {
7   console.log(response); // success
8 });

Handlers passed to promise.then don’t just handle the result of the previous promise-they return is turned into a new promise.

 1 let promise = new Promise(resolve => {
 2   let value = 'success';
 3   resolve(value);
 4 });
 5 
 6 promise.then(response => {
 7   console.log(response); // success
 8   return 'another success';
 9 }).then(response => {
10   console.log(response); // another success
11 });

You can see, that the code based on promises is always flat. No more callback hell.

If you are only interested in rejections, you can omit the first parameter.

 1 let promise = new Promise((resolve, reject) => {
 2   let reason = 'failure';
 3   reject(reason);
 4 });
 5 
 6 promise.then(
 7   null,
 8   error => {
 9     console.log(error); // failure
10   }
11 );

But is a more compact way of doing the same thing-catch() method.

1 let promise = new Promise((resolve, reject) => {
2   let reason = 'failure';
3   reject(reason);
4 });
5 
6 promise.catch(err => {
7   console.log(err); // failure
8 });

If we have more than one then() call, the error is passed on until there is an error handler.

 1 let promise = new Promise(resolve => {
 2   resolve();
 3 });
 4 
 5 promise
 6   .then(response => {
 7     return 1;
 8   })
 9   .then(response => {
10     throw new Error('failure');
11   })
12   .catch(error => {
13     console.log(error.message); // failure
14   });

We can even combine one or more promises into new promises without having to take care of ordering of the underlying asynchronous operations yourself.

 1 let doSmth = new Promise(resolve => {
 2     resolve('doSmth');
 3   }),
 4   doSmthElse = new Promise(resolve => {
 5     resolve('doSmthElse');
 6   }),
 7   oneMore = new Promise(resolve => {
 8     resolve('oneMore');
 9   });
10 
11 Promise.all([
12     doSmth,
13     doSmthElse,
14     oneMore
15   ])
16   .then(response => {
17     let [one, two, three] = response;
18     console.log(one, two, three); // doSmth doSmthElse oneMore
19   });

Promise.all() takes an array of promises and when all of them are fulfilled, it put their values into the array.

There are two more functions which are useful:

  • Promise.resolve(value) - it returns a promise which resolves to a value or returns value if value is already a promise
  • Promise.reject(value) - returns rejected promise with value as value

Pitfall

Promises have its pitfall as well. Let’s image that when any exception is thrown within a then or the function passed to new Promise, will be silently disposed of unless manually handled.

set, map, weak

Sets and maps will be (are) finally available in ES6! No more spartan way to manipulate data structures. This chapter explains how we can deal with Map, Set, WeakMap and WeakSet.

Map

Maps are a store for key / value pairs. Key and value could be a primitives or object references.

Let’s create a map:

 1 let map = new Map(),
 2     val2 = 'val2',
 3     val3 = {
 4       key: 'value'
 5     };
 6 
 7 map.set(0, 'val1');
 8 map.set('1', val2);
 9 map.set({ key: 2 }, val3);
10 
11 console.log(map); // Map {0 => 'val1', '1' => 'val2', Object {key: 2} => Object \
12 {key: 'value'}}

We can also use a constructor to create the sam map, based on array param passed to the constructor:

 1 let map,
 2     val2 = 'val2',
 3     val3 = {
 4       key: 'value'
 5     };
 6 
 7 map = new Map([[0, 'val1'], ['1', val2], [{ key: 2 }, val3]]);
 8 
 9 console.log(map); // Map {0 => 'val1', '1' => 'val2', Object {key: 2} => Object \
10 {key: 'value'}}

To get a value by using a key, we have to use a get() method to do it (surprising):

 1 let map = new Map(),
 2     val2 = 'val2',
 3     val3 = {
 4       key: 'value'
 5     };
 6 
 7 map.set(0, 'val1');
 8 map.set('1', val2);
 9 map.set({ key: 2 }, val3);
10 
11 console.log(map.get('1')); // val2

To iterate over the map collection, we can use built-in forEach method or use new for..of structure:

 1 // forEach
 2 let map = new Map(),
 3     val2 = 'val2',
 4     val3 = {
 5       key: 'value'
 6     };
 7 
 8 map.set(0, 'val1');
 9 map.set('1', val2);
10 map.set({ key: 2 }, val3);
11 
12 map.forEach(function (value, key) {
13   console.log(`Key: ${key} has value: ${value}`);
14   // Key: 0 has value: val1
15   // Key: 1 has value: val2
16   // Key: [object Object] has value: [object Object]
17 });
 1 // for..of
 2 let map = new Map(),
 3     val2 = 'val2',
 4     val3 = {
 5       key: 'value'
 6     };
 7 
 8 map.set(0, 'val1');
 9 map.set('1', val2);
10 map.set({ key: 2 }, val3);
11 
12 for (let entry of map) {
13   console.log(`Key: ${entry[0]} has value: ${entry[1]}`);
14   // Key: 0 has value: val1
15   // Key: 1 has value: val2
16   // Key: [object Object] has value: [object Object]
17 };

We can also use a couple of methods to iterate:

  • entries() — get all entries
  • keys() — get only all keys
  • values() — get only all values

To check if value is stored in our map, we can use has() method:

 1 let map = new Map(),
 2     val2 = 'val2',
 3     val3 = {
 4       key: 'value'
 5     };
 6 
 7 map.set(0, 'val1');
 8 map.set('1', val2);
 9 map.set({ key: 2 }, val3);
10 
11 console.log(map.has(0));     // true
12 console.log(map.has('key')); // false

To delete entry, we have delete() method:

 1 let map = new Map(),
 2     val2 = 'val2',
 3     val3 = {
 4       key: 'value'
 5     };
 6 
 7 map.set(0, 'val1');
 8 map.set('1', val2);
 9 map.set({ key: 2 }, val3);
10 
11 console.log(map.size); // 3
12 
13 map.delete('1');
14 
15 console.log(map.size); // 2

..and to clear all collection, we use clear() method:

 1  let map = new Map(),
 2      val2 = 'val2',
 3      val3 = {
 4        key: 'value'
 5      };
 6 
 7  map.set(0, 'val1');
 8  map.set('1', val2);
 9  map.set({ key: 2 }, val3);
10 
11  console.log(map.size); // 3
12 
13  map.clear();
14 
15  console.log(map.size); // 0

Set

It’s a collection for unique values. The values could be also a primitives or object references.

1 let set = new Set();
2 
3 set.add(1);
4 set.add('1');
5 set.add({ key: 'value' });
6 
7 console.log(set); // Set {1, '1', Object {key: 'value'}}

Like a map, set allows to create collection by passing an array to its constructor:

1 let set = new Set([1, '1', { key: 'value' }]);
2 
3 console.log(set); // Set {1, '1', Object {key: 'value'}}

To iterate over sets we have the same two options — built-in forEach function or for..of structure:

1 // forEach
2 let set = new Set([1, '1', { key: 'value' }]);
3 
4 set.forEach(function (value) {
5   console.log(value);
6   // 1
7   // '1'
8   // Object {key: 'value'}
9 });
1 // for..of
2 let set = new Set([1, '1', { key: 'value' }]);
3 
4 for (let value of set) {
5   console.log(value);
6   // 1
7   // '1'
8   // Object {key: 'value'}
9 };

Set doesn’t allow to add duplicates.

1 let set = new Set([1, 1, 1, 2, 5, 5, 6, 9]);
2 console.log(set.size); // 5!

We can also use has(), delete(), clear() methods, which are similar to the Map versions.

WeakMap

WeakMaps provides leak-free object keyed side tables. It’s a Map that doesn’t prevent its keys from being garbage-collected. We don’t have to worry about memory leaks.

If the object is destroyed, the garbage collector removes an entry from the WeakMap and frees memory.

Keys must be objects.

It has almost the same API like a Map, but we can’t iterate over the WeakMap collection. We can’t even determine the length of the collection because we don’t have size attribute here.

The API looks like this:

1 new WeakMap([iterable])
2 
3 WeakMap.prototype.get(key)        : any
4 WeakMap.prototype.set(key, value) : this
5 WeakMap.prototype.has(key)        : boolean
6 WeakMap.prototype.delete(key)     : boolean
 1 let wm = new WeakMap(),
 2     obj = {
 3       key1: {
 4         k: 'v1'
 5       },
 6       key2: {
 7         k: 'v2'
 8       }
 9    };
10 
11 wm.set(obj.key1, 'val1');
12 wm.set(obj.key2, 'val2');
13 
14 console.log(wm); // WeakMap {Object {k: 'v1'} => 'val1', Object {k: 'v2'} => 'va\
15 l2'}
16 console.log(wm.has(obj.key1)); // true
17 
18 delete obj.key1;
19 
20 console.log(wm.has(obj.key1)); // false

WeakSet

Like a WeakMap, WeakSet is a Seat that doesn’t prevent its values from being garbage-collected. It has simpler API than WeakMap, because has only three methods:

1 new WeakSet([iterable])
2 
3 WeakSet.prototype.add(value)    : any
4 WeakSet.prototype.has(value)    : boolean
5 WeakSet.prototype.delete(value) : boolean

WeakSets are collections of unique objects only.

WeakSet collection can’t be iterated and we cannot determine its size.

 1 let ws = new WeakSet(),
 2     obj = {
 3       key1: {
 4         k: 'v1'
 5       },
 6       key2: {
 7         k: 'v2'
 8       }
 9    };
10 
11 ws.add(obj.key1);
12 ws.add(obj.key2);
13 
14 console.log(ws); // WeakSet {Object {k: 'v1'}, Object {k: 'v2'}}
15 console.log(ws.has(obj.key1)); // true
16 
17 delete obj.key1;
18 
19 console.log(ws.has(obj.key1)); // false