2014-08-01

Using ECMAScript 6 today

This blog post is an early draft of my upcoming book “Exploring ES6”. It is a TOC-like overview, with links leading to “chapters” with more material.

Updates:

  • [2015-01-19] New sections on proxies and symbols; more content for object literals and template strings; a new FAQ at the end of this post.
  • [2014-11-05] Restructured the post and added more content (terminology, let, promises, ES7+).
  • [2014-09-02] Rewrote Sect. “More material on ECMAScript 6”; mentioned iterators, generators and promises.

ECMAScript 6 (ES6) still sounds like something from the future. After all, it will only become a standard by mid 2015. However, its features are continually appearing in browsers and there are compilers that translate ES6 code to ES5 code. The latter is already a compelling solution, because the ECMAScript 6 feature set is already frozen.

This blog post gives a brief overview of ECMAScript 6 features and describes tools that enable you to use them today.

Terminology

  • TC39 (Ecma Technical Committee 39): the committee evolving JavaScript.
    • Members: companies (all major browser vendors etc.).
    • Meetings: attended by employees and invited experts.
  • ECMAScript: the official name of the language
    • Versions: ECMAScript 5 is short for “ECMAScript Language Specification, Edition 5”
  • JavaScript:
    • colloquially: the language
    • formally: one implementation of ECMAScript
  • ECMAScript Harmony: improvements after ECMAScript 5 (ECMAScript 6 and 7)
  • ECMAScript.next: code name for ECMAScript 6 (until version number and feature set was clearer)

More information:

ECMAScript 6 highlights

This section gives an overview of most ES6 features.

New syntax

New object literal features

Property value shorthands (used for destructuring in later examples):

    let first = 'Jane';
    let last = 'Doe';
    
    let obj = { first, last };
    // Same as:
    let obj = { first: first, last: last };

Method definitions:

    let obj = {
        myMethod(arg0, arg1) {
            ...
        }
    };

Computed property keys:

    let propKey = 'foo';
    let obj = {
        [propKey]: true,
        ['b'+'ar']: 123
    };

This new syntax can also be combined with a method definition:

    let obj = {
        ['h'+'ello']() {
            return 'hi';
        }
    };
    console.log(obj.hello()); // hi

The main use case for computed property keys is to make it easy to use symbols as property keys.

Classes

Classes:

    // Superclass
    class Person {
        constructor(name) {
            this.name = name;
        }
        describe() {
            return 'Person called ' + this.name;
        }
    }
    
    // Subclass
    class Employee extends Person {
        constructor(name, title) {
            super(name); 
            this.title = title;
        }
        describe() {
            return super.describe() + ' (' + this.title + ')';
        }
    }

Subclassing built-ins such as Error and Array:

    class MyError extends Error {
        // ...
    }
Arrow functions

Arrow functions:

    // More compact syntax
    let arr = [1, 2, 3];
    let squares = arr.map(x => x * x);
    
    // `this` is picked up from surroundings (lexical)
    // Therefore: no more `that = this` or bind()
    function UiComponent {
        let button = document.getElementById('myButton');
        button.addEventListener('click', () => {
            console.log('CLICK');
            this.handleClick(); // lexical `this`
        });
    }
Modules

Modules:

    // lib.js
    export const sqrt = Math.sqrt;
    export function square(x) {
        return x * x;
    }
    export function diag(x, y) {
        return sqrt(square(x) + square(y));
    }
    
    // main.js
    import { square, diag } from 'lib';
    console.log(square(11)); // 121
    console.log(diag(4, 3)); // 5
Variables, assignment and parameter handling

Block-scoped variables via let (writable) and const (read-only):

    function order(x, y) {
        if (x > y) {
            let tmp = x;
            x = y;
            y = tmp;
        }
        console.log(tmp===x); // ReferenceError: tmp is not defined
        return [x, y];
    }

Spread operator:

    let arr = [-1, 7, 2];
    let highest = Math.max(...arr); // 7
    
    new Date(...[2011, 11, 24]) // 2011, December 24, 00:00
    
    // Non-destructively concatenate single elements
    let arr2 = [...arr, 9, -6]; // [-1, 7, 2, 9, -6]

Destructuring:

    let [all, year, month, day] =
        /^(\d\d\d\d)-(\d\d)-(\d\d)$/
        .exec('2999-12-31');
    
    let { first, last } = { first: 'Jane', last: 'Doe' };

Default parameter values:

    function findClosestShape(x=0, y=0) {
        // ...
    }

Rest parameters:

    function format(pattern, ...params) {
        return params;
    }
    console.log(format('a', 'b', 'c')); // ['b', 'c']

Named parameters via destructuring:

    class Entries {
        // ...
        selectEntries({ from = 0, to = this.length } = {}) {
        // Long: { from: from=0, to: to=this.length }
    
            // Use `from` and `to`
        }
    }
    let entries = new Entries();
    entries.selectEntries({ from: 5, to: 15 });
    entries.selectEntries({ from: 5 });
    entries.selectEntries({ to: 15 });
    entries.selectEntries({});
    entries.selectEntries();
Loops, iteration, generators

for-of loop (works for all objects that implement the ES6 iteration protocol):

    let arr = ['foo', 'bar', 'baz'];
    
    for (let element of arr) {
        console.log(element);
    }
    /* Output:
       foo
       bar
       baz
    */
    
    for (let [index,element] of arr.entries()) {
        console.log(`${index}. ${element}`);
    }
    /* Output:
       0. foo
       1. bar
       2. baz
    */

Iterators and generators (iterators are a protocol for retrieving the contents of a collection which is supported by the for-of loop; generators are “pausable functions” that help with implementing iterators and more):

    // Generator function, implementing an iterator over objects
    function* objectEntries(obj) {
        // In ES6, you can use strings or symbols as property keys,
        // Reflect.ownKeys() retrieves both
        for (let key of Reflect.ownKeys(obj)) {
            yield [key, obj[key]]; // pause and return a value
        }
    }
    
    let obj = { first: 'Jane', last: 'Doe' };
    
    for (let [key,value] of objectEntries(obj)) {
        console.log(`${key}. ${value}`);
    }
    /* Output:
       first. Jane
       last. Doe
    */
Template strings

Template strings let you interpolate arbitrary expressions into a string:

    let name = 'Jane';
    let str = `Hi ${name}! 3 plus 4 is ${3+4}`;

A template string can span multiple lines:

    let text = `This is a text
    that spans
    multiple lines.`;

If a template string is prefixed with an identifier (a tag), it becomes a function call: The function referred to by the identifier (the tag handler) is called with the static pieces between the ${} and the results of the expressions inside them. A tag handler can decide whether to accept the static pieces verbatim (“raw”) or with escapes such as \n interpreted (“cooked”). That lets you implement small domain-specific languages. For example, the tag XRegExp.rx is a hypothetical nicer interface to the XRegExp regular expression library:

    var parts = '/2012/10/Page.html'.match(XRegExp.rx`
        ^ # match at start of string only
        / (?<year> [^/]+ ) # capture top dir name as year
        / (?<month> [^/]+ ) # capture subdir name as month
        / (?<title> [^/]+ ) # capture base name as title
        \.html? $ # .htm or .html file ext at end of path
    `);
    console.log(parts.year); // 2012

Two features stand out:

  • The content inside the backticks spans multiple lines.
  • The backslash at the beginning of the last line (\.html?) is a regular expression backslash. Inside a string literal, it would have to be written as \\.
Symbols

Symbols are a new primitive type in JavaScript. They mainly serve as unique (clash-free) names for properties. For example, an object is marked as iterable via a method whose key is (the symbol stored in) Symbol.iterator. This key cannot accidentally clash with any other property key:

    let obj = {
        data: [ 'hello', 'world' ],
        [Symbol.iterator]() {
            const self = this;
            let index = 0;
            return {
                next() {
                    if (index < self.data.length) {
                        return {
                            value: self.data[index++]
                        };
                    } else {
                        return { done: true };
                    }
                }
            };
        }
    };

The iterability of obj enables you to use the for-of loop and similar JavaScript features:

    for (let x of obj) {
        console.log(x);
    }
    // Output:
    // hello
    // world
Proxies

Proxies enable you to intercept and customize operations performed on objects (such as getting properties). They are a meta programming feature.

In the following example, the handler intercepts the operation get (getting properties).

    let target = {};
    let handler = {
        get(target, propKey, receiver) {
            console.log('get ' + propKey);
            return 123;
        }
    };
    let proxy = new Proxy(target, handler);

When we get the property proxy.foo, the handler intercepts that operation:

    > proxy.foo
    get foo
    123

New functionality in the standard library

Utility methods

ECMAScript 6 has several new utility methods. This section demonstrates a few of them.

Object.assign():

    class Point {
        constructor(x, y) {
            Object.assign(this, { x, y });
            // ES6: { x, y } is abbrev. for { x: x, y: y }
        }
    }

Array.prototype.findIndex():

    > [6, 8, -5].findIndex(x => x < 0)
    2
    > [6, 8, 5].findIndex(x => x < 0)
    -1

Array.prototype.fill():

    > ['a', 'b', 'c'].fill(7)
    [ 7, 7, 7 ]
    > new Array(3).fill(7)
    [ 7, 7, 7 ]

New string methods:

    > 'hello world'.startsWith('hello')
    true
    > '*'.repeat(5)
    '*****'
Collections

The keys of a Map can be arbitrary values:

    > let obj = {};
    > let map = new Map();
    
    > map.set(obj, 123);
    > map.get(obj)
    123
    > map.has(obj)
    true
    > map.delete(obj);
    true
    > map.has(obj)
    false

A Set is a collection of unique elements:

    let arr = [5, 1, 5, 7, 7, 5];
    let unique = [...new Set(arr)]; // [ 5, 1, 7 ]

More collections:

  • A WeakMap is a map that doesn’t prevent its keys from being garbage-collected. That means that you can associate data with objects without having to worry about memory leaks.
  • A WeakSet is a set that doesn’t prevent its elements from being garbage-collected.
Asynchronous programming via promises

Promises: an API that helps with asynchronous programming. Quoting “JavaScript Promises: There and back again” by Jake Archibald:

all new DOM APIs with async success/failure methods will use promises. This is happening already with Quota Management, Font Load Events, ServiceWorker, Web MIDI, Streams, and more.

The following is an example of using promises: a function httpGet() that retrieves a resource via HTTP GET (the current way of doing this is via XMLHttpRequest):

    httpGet('http://example.com/file.txt')
    .then(
        value => {
            console.log('Contents: ' + value);
        },
        reason => {
            console.error('Something went wrong', reason);
        });

This asynchronous function could be implemented like this:

    function httpGet(url) {
        return new Promise(
            function (resolve, reject) {
                var request = new XMLHttpRequest();
                request.onreadystatechange = function () {
                    if (this.status === 200) {
                        // Success
                        resolve(this.response);
                    } else {
                        // Something went wrong (404 etc.)
                        reject(new Error(this.statusText));
                    }
                }
                request.onerror = function () {
                    reject(new Error(
                        'XMLHttpRequest Error: '+this.statusText));
                };
                request.open('GET', url);
                request.send();    
            });
    }

Promises are explained in two blog posts:

More Material on ECMAScript 6

Deploying ES6

How to use ES6 today is described in the blog post “Deploying ECMAScript 6”.

ECMAScript 7 and later

Starting with ECMAScript 7, TC39 will time-box releases. The plan is to release a new version of ECMAScript every year, with whatever features are ready at that time. That will result in much smaller releases.

Work on ECMAScript 7+ has already begun:

  • Proposals are listed on GitHub.
  • A document describes the process that is used by TC39, starting with ES7, to create new ECMAScript versions.

Additionally, Microsoft, Facebook and Google are exploring optional static typing for JavaScript. Their approaches are lightweight and similar mechanisms may eventually become part of the language:

FAQ

How can ECMAScript 5 and ECMAScript 6 exist side by side?

ECMAScript 6 is completely backwards compatible, it is a superset of ECMAScript 5. Details of how the new ES6 features were added to both strict mode and non-strict mode are explained in the blog post “One JavaScript: avoiding versioning in ECMAScript 6”.

Does it still make sense to learn ECMAScript 5?

As we have seen, you can already exclusively write code in ECMAScript 6 and avoid older versions of JavaScript. Does that mean that you shouldn’t learn ECMAScript 5, anymore? Alas, it doesn’t, for several reasons:

  • ECMAScript 6 is a superset of ECMAScript 5 – new JavaScript versions must never break existing code. Thus, nothing you learn about ECMAScript 5 is learned in vain.

  • There are several ECMAScript 6 features that kind of replace ECMAScript 5 features, but still use them as their foundation. Two examples: classes are internally translated to constructors and methods are still functions (as they have always been).

  • As long as ECMAScript 6 is compiled to ECMAScript 5, it is useful to understand the output of the compilation process. And you’ll have to compile to ES5 for a while (probably years), until you can rely on ES6 being available in all relevant browsers, in the same manner in that you can usually rely on ES5 now.

  • It’s important to be able to understand legacy code.

Will JavaScript ever be statically typed?

Maybe. Three experiments with statically typing JavaScript are explained in the blog post “Statically typed JavaScript via Microsoft TypeScript, Facebook Flow and Google AtScript”.

1 comment:

Axel Rauschmayer said...

Thank you for Great article