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:
- Arrow functions
- Block-level scoping
- Classes
- Destructuring
- Enhanced object literals
for-of
loop- Generators
- Iterators & iterables
- Modules
- New APIs
- New Collections
- Parameter handling
- Promises
- Template literals & tagged templates
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, 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
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
// 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 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
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
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
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 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
Provide a modular of organizing and loading JavaScript code
// code example coming soon
New APIs for existing native JavaScript classes Math
, Object
, RegExp
, etc.
// code example coming soon
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
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
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
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