Repo for all the code used in the Learning ES6 blog series
Switch branches/tags
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
examples
json
.gitignore
README.md
index.html

README.md

Learning ES6

ECMAScript 6 is the new version of JavaScript making its way into the interpreters of our modern browsers and servers. The specification is filled with lots of new features; much more than ECMAScript 5 which came out way back in 2009. The Learning ES6 blog series is walking through all of the major features in significant detail to hopefully provide a deeper level of understanding to features you may have already heard about. This repo contains all the code examples used throughout the series.

The following, however, is a listing of all of the ES6 features and the basic way in which the feature is used:

Using ES6 right now

Support for ES6 functionality in JS engines is growing every week and kept up to date by Kangax’s ES6 compatibility matrix. However:

  • ES6 support is still fairly low across browsers & servers (max is less than 70%)
  • The features that are supported differ between browsers (with some overlap)
  • None of the IE browsers significantly support ES6 (the new Microsoft Edge browser does)

As a result, you cannot yet reliable run ES6 natively client- or server-side. Your best bet is compiling your ES6 code down to ES5 using transpilation tools like Babel, Traceur or TypeScript as part of your build process.

More info: Blog post

Arrow functions

Arrow functions, aka "fat arrows", are more or less a shorthand form of anonymous function expressions that already exist in JavaScript. The best thing about arrow functions, aside from the terse syntax, is that this uses lexical scoping; its value is always “inherited” from the enclosing scope.

// Expression syntax
var squares = [1, 2, 3].map(x => x * x);
var sum = [9, 8, 7].reduce((memo, value) => memo + value, 0);
var getRandom = () => Math.random() * 100;

// Block syntax
$("#deleteButton").click(event => {
    if (confirm(“Are you sure?”)) {
        clearAll();
    }
});

// Lexical this binding
var car = {
    speed: 0,
    accelerate: function() {
        this.accelerator = setInterval(
            () => {
                // *this* is the same as it is outside
                // of the arrow function!
                this.speed++;
                console.log(this.speed);
            },
            100
        );
    },
    cruise: function() {
        clearInterval(this.accelerator);
        console.log('cruising at ' + this.speed + ' mph');
    }
};

More info: Blog post | Browser examples | Source code

Block-level scoping

let is the new var. By using block-level scoping, let and const help developers avoid common mistakes they make not because they write bad code, but because they don’t fully understand the idiosyncrasies of how JavaScript handles variables. Variables declared via let are not available outside of the block in which they are declared. Variables declared via const also cannot be updated. These pretty much replace the ES3 or ES5 way of declaring variables using var.

function simpleExample(value) {
    const constValue = value;

    if (value) {
        var varValue = value;
        let letValue = value;

        console.log('inside block', varValue, letValue);
    }

    console.log('outside block');

    // varValue is available even though it was defined
    // in if-block because it was "hoisted" to function scope
    console.log(varValue);

    try {
        // letValue is a ReferenceError because it
        // was defined w/in if-block
        console.log(letValue);
    }
    catch (e) {
        // e is a ReferenceError
        console.log('letValue not accessible', e);
    }

    // SyntaxError to try and update a variable
    // declared via const
    //constValue += 1;
}

More info: Blog post | Browser examples | Source code

Classes

// Define base Note class
class Note {
	constructor(id, content, owner) {
		if (new.target === Note) {
			throw new Error('Note cannot be directly constructed.')
		}

		this._id = id;
		this._content = content;
		this._owner = owner;
	}

	static add(...properties) {
		// `this` will be the class on which `add()` was called
		// increment counter
		++this._idCounter;

		let id = `note${this._idCounter}`;

		// construct a new instance of the note passing in the
		// arguments after the ID. This is so subclasses can
		// get all of the arguments needed
		let note = new this(id, ...properties);

		// add note to the lookup by ID
		this._noteLookup[id] = note;

		return note;
	}

	static get(id) {
		return this._noteLookup[id];
	}

	// read-only
	get id() { return this._id; }

	get content() { return this._content; }
	set content(value) { this._content = value; }

	get owner() { return this._owner; }
	set owner(value) { this._owner = value; }

	toString() {
		return `ID: ${this._id}
			Content: ${this._content}
			Owner: ${this._owner}`;
	}
}

// Static "private" properties (not yet supported in class syntax)
Note._idCounter = -1;
Note._noteLookup = {};

class ColorNote extends Note {
	constructor(id, content, owner, color='#ff0000') {
		// super constructor must be called first!
		super(id, content, owner);
		this._color = color;
	}

	get color() { return this._color; }
	set color(value) { this._color = value; }

	toString() {  // computed method names are supported
		// Override `toString()`, but call parent/super version
		// first
		return `${super.toString()}
			Color: ${this._color}`;
	}
}

// `add` factory method is defined on `Note`, but accessible
// on ColorNote subclass
var colorNote = ColorNote.add('My note', 'benmvp', '#0000ff');

// output: ID: note0
// Content: My Note
// Owner: benmvp
// Color: #0000ff
console.log(`${colorNote}`);

// output: true
console.log(Note.get('note0') === colorNote);

More info: Blog post | Browser examples | Source code

Destructuring

Destructuring makes it easier to work with objects and arrays in JavaScript. Using a pattern syntax similar to object and array literals, we can poke into data structures and pick out the information we want into variables.

// Object pattern matching
let {lName, fName} = {fName: 'John', age: 15, lName: 'Doe'};

// output: Doe, John
console.log(lName + ', '+ fName);

// Array pattern matching
let [first, second, third] = [8, 4, 100, -5, 20];

// output: 100, 4, 8
console.log(third, second, first);

More info: Blog post | Browser examples | Source code

Enhanced object literals

ECMAScript 6 makes declaring object literals even more succinct by providing shorthand syntax for initializing properties from variables and defining function methods. It also enables the ability to have computed property keys in an object literal definition.

function getCar(make, model, value) {
    return {
        // with property value shorthand
        // syntax, you can omit the property
        // value if key matches variable
        // name
        make,  // same as make: make
        model, // same as model: model
        value, // same as value: value

        // computed values now work with
        // object literals
        ['make' + make]: true,

        // Method definition shorthand syntax
        // omits `function` keyword & colon
        depreciate() {
            this.value -= 2500;
        }
    };
}

let car = getCar('Kia', 'Sorento', 40000);

// output: {
//     make: 'Kia',
//     model:'Sorento',
//     value: 40000,
//     makeKia: true,
//     depreciate: function()
// }
console.log(car);

car.depreciate();

// output: 37500
console.log(car.value);

More info: Blog post | Browser examples | Source code

for-of loop

The new for-of loop introduced with ES6 allows for iterating over an array (or any iterable) in a succinct fashion similar to how we can iterate over the keys of an object using for-in.

let list = [8, 3, 11, 9, 6];

for (let value of list) {
  console.log(value);
}

More info: Blog post

Generators

A generator function is a special type of function that when invoked automatically generates a special iterator, called a generator. Generator functions are indicated by function* and make use of the yield operator to indicate the value to return for each successive call to .next() on the generator.

function* range(start, count) {
    for (let delta = 0; delta < count; delta++) {
        yield start + delta;
    }
}

for (let teenageYear of range(13, 7)) {
    console.log(`Teenage angst @ ${teenageYear}!`);
}

More info: Blog post | Browser examples | Iterators source code

Iterators & iterables

Iterators provide a simple way to return a (potentially unbounded) sequence of values. The @@iterator symbol is used to define default iterators for objects, making them an iterable.

class MyIterator {
    constructor() {
        this.step = 0;
    }
    [Symbol.iterator]() {
        return this;
    }
    next() {
        this.step++;

        if (this.step === 1)
            return {value: 'Ben'};
        else if (this.step == 2)
            return {value: 'Ilegbodu'};

        return {done: true};
    }
}

let iter = new MyIterator();

// output: {value: 'Ben'}
console.log(iter.next());

// output: {value: 'Ilegbodu'}
console.log(iter.next());

// output: {done: true}
console.log(iter.next());

// output: {done: true}
console.log(iter.next());

The iteration & iterable protocol are based on the following duck-typed interfaces (explained in TypeScript for clarity):

interface Iterable {
    [System.iterator]() : Iterator;
}
interface Iterator {
    next() : IteratorResult;
    return?(value? : any) : IteratorResult;
}
interface IteratorResult {
    value : any;
    done : boolean;
}

All the collection types (Array, Map, Set, etc.) have default iterators designed for easy access to their contents. Strings also have a default iterator so it’s easy to iterate over the code point characters of the string (rather than the code unit characters).

let str = '😍😎🙏';

for (let char of str) {
    console.log(char);
}

// output:
// 😍
// 😎
// 🙏

More info: Blog post | Browser examples | Source code

Modules

Provide a modular of organizing and loading JavaScript code

// code example coming soon

New APIs

New APIs for existing native JavaScript classes Math, Object, RegExp, etc.

// code example coming soon

New Collections

ES6 introduces four new efficient collection data structures to mitigate our ab-use of object and array literals.

A Set contains a unique set of values of any type:

let set = new Set([true, 'Ben', 5]);

set.add(false).add('Ilegbodu').add(25).add(true);

// output: 6
console.log(set.size);

// output: true
console.log(set.has('Ben'));

Map provides a mapping of keys of any type to values of any type:

let map = new Map();

map.set('foo', 'bar');
map.set(true, 'Ben'); // non-strings can be keys

// output: Ben
console.log(map.get(true));

// output: 2
console.log(map.size);

WeakMap provides memory leak-free lookup of objects to values of any type:

let $leftButton = $('#leftButton');
let domMetadata = new WeakMap();

domMetadata.set($leftButton, {clickCount:0});

WeakSet provides memory leak-free collection of unique objects:

let $leftButton = $('#leftButton');
let clickedDomNodes = new WeakSet();

clickedDomNodes.add($leftButton);

More info: Blog post | Browser examples | Source code

Parameter handling

ES6 allows for function headers to define default values for parameters, marking them as optional:

function getData(data, useCache=true) {
    if (useCache) {
        console.log('using cache for', data);
    }
    else {
        console.log('not using cache', data);
    }
}

// `useCache` is missing and is `undefined`.
// therefore `useCache `defaults to `true`
getData({q:'churches+in+Pittsburg'});

Rest parameters should complete replace the need for the problematic arguments special variable:

function join(separator, ...values) {
    return values.join(separator);
}

// all of the parameters after the first
// are gathered together into `values`
// which is a true `Array`
// output: "one//two//three"
console.log(join('//', 'one', 'two', 'three'));

We should no longer need the apply function with the new spread operator:

function volume(width, length, height) {
    return width * length * height;
};

// the array values are separated into
// separate parameters
// output: 80 (2 * 8 * 5)
console.log(volume(...[2, 8, 5]));

Lastly, object destructuring with function parameters allows us to simulate named parameters:

let ajax = function(url, {method, delay, callback}) {
    console.log(url, method, delay);
    setTimeout(
        () => callback('DONE!'),
        delay
    );
};

// the second parameter to the function
// is an object whose properties are
// destructured to individual variables
// simulating named parameters
ajax(
    'http://api.eventbrite.com/get',
    {
        delay: 2000,
        method: 'POST',
        callback: function(message) {
            console.log(message);
        }
    }
);

More info: Blog post | Browser examples | Source code

Promises

A promise represents the eventual result of an asynchronous operation. Instead of registering a callback in the call to an async function, the function returns a promise. The caller registers callbacks with the promise to receive either a promise's eventual value from the async operation or the reason why the promise cannot be fulfilled.

// Creating a promise wrapper for setTimeout
function wait(delay = 0) {
    return new Promise((resolve, reject) => {
        setTimeout(resolve, delay);
    });
}

// Using a promise
wait(3000)
    .then(() => {
        console.log('3 seconds have passed!');
        return wait(2000);
    })
    .then(() => {
    	console.log('5 seconds have passed!');
    	x++; // ReferenceError triggers `catch`
    })
    .catch(error => {
    	// output: ReferenceError
    	console.log(error);
    })
    .then(() => {
    	// simulate `finally` clause
    	console.log('clean up');
    });

More info: Blog post | Browser examples | Source code

Template literals & tagged templates

ES6 template literals are a brand new type of string literal, delimited by backticks (`), that natively support string interpolation (token substitution) and multi-line strings. And because they use backticks as a delimiter, they can include single and double quotes without needing to escape them.

let firstName = 'Ben',
    lastName = `Ilegbodu`;

// Basic template literal is surrounding by
// backticks so single/double quotes do need
// to be escaped
// output: He said, "It's your fault!"
console.log(`He said, "It's your fault!"`);

// Template literals support interpolation.
// The values within `firstName` and `lastName`
// are substituted into where the tokens are
// output: Name: Ilegbodu, Ben
console.log(`Name: ${lastName}, ${firstName}`);

// Template literals support multi-line strings
// output: This is
//      multi-line text, so that
//      newline characters are
//
//
//      not needed. All whitespace
//          is respected, including tabs.
//
//
console.log(`This is
    multi-line text, so that
    newline characters are


    not needed. All whitespace
        is respected, including tabs.

`);

ES6 also supports tagged templates, which are created by prefixing a template literal with the name of a function (called the tag function). That functions receives an array of tokenized string literals plus the substitution values, enabling custom string interpolation or processing.

function l10n(literals, ...substitutions) {
    // return interpolated string with
    // literals translated to native language
    // and localized to locale
}

let cost = 10.45,
    date = new Date('12/1/2016');

// translate and localize
// The function name (l10n) prefixes the
// template literal
// English: Your ticket for 12.1.2016 is $10.45.
// Spanish: Su billete para el 2016.12.1 es €10,45.
console.log(l10n`Your ticket for ${date} is {$cost}:c.`);

More info: Blog post | Browser examples | Source code