An introduction to ES6

Yieldmo

Mani Nilchiani
03.03.2016

The Structure of this Presentation

  • History
  • Intro to a select subset of features, categorized topically
    When possible, I pair the before / after syntax
  • How can we use ES6 today?
  • Resources & Credits

History

  • 1995: JavaScript is born as LiveScript
  • 1997: ECMAScript standard is established
  • 1999: ES3 comes out
  • 2000–2005: XMLHttpRequest, a.k.a. AJAX, gains popularity
  • 2009: ES5 comes out
  • 2015: ES6/ECMAScript2015 comes out

Featured features. (Haha)
go here for full list

  1. Syntax
  2. Organization
  3. Async Flow Control
  4. API Additions
  5. Meta Programming

1. Syntax

Block-Scoped variables

Traditionally in JS, only functions create scope using `var`. ES6 introduces `let` and `const`, block scoped variables.


var a = 2;

(function IIFE(){
    var a = 3;
    console.log( a );   // 3
})();

console.log( a );       // 2

let i = 55;

for(let i = 0; i < 10; i++) {
	console.log(i); // 0 1 2 3 4 5 6 7 8 9
}

console.log(i); // 55

const foo = 'abc';
foo = 'def'; // TypeError

For `const`, the value is not frozen or immutable because of const, just the assignment of it. If the value is complex, such as an object or array, the contents of the value can still be modified:


const a = [1,2,3];
a.push( 4 );
console.log( a ); // [1,2,3,4]
a = 42; // TypeError

Spread/Rest

ES6 introduces a new `...` operator that's typically referred to as the spread or rest operator, depending on where/how it's used.
In ES5 we would do:


function foo(x,y,z) {
    console.log( x, y, z );
}
foo.apply( null, [1,2,3] ); // 1 2 3

In ES6 we can simply:


foo( ...[1,2,3] ); // 1 2 3

But `...` can be used to spread out/expand a value in other contexts as well, such as inside another array declaration.
ES5:


var a = [2,3,4];
var b = [1].concat(a, [5]); // [1,2,3,4,5]

ES6:


var a = [2,3,4];
var b = [ 1, ...a, 5 ];

console.log( b ); // [1,2,3,4,5]

Instead of spreading a value out, the `...` can gather a set of values together into an array.


function foo(x, y, ...z) {
    console.log( x, y, z );
}

foo( 1, 2, 3, 4, 5 ); // 1 2 [3,4,5]

Which means that you can gather your `arguments` if they are unnamed


function foo(...args) {
    console.log( args );
}

foo( 1, 2, 3, 4, 5); // [1,2,3,4,5]

Default Parameter Values

One of the most common idioms in JavaScript relates to setting a default value for a function parameter.
ES5:


function foo(x,y) {
  x = x || 11;
  y = y || 31;

  console.log( x + y );
}

foo(); // 42
foo( 5, 6 ); // 11
foo(0, 42); // 53 -- YIKES!

ES6:


function foo(x = 11, y = 31) {
    console.log( x + y );
}

foo(); // 42
foo( 5, 6 ); // 11
foo( 0, 42 ); // 42 -- WOOT!

Default Value Expressions

Function default values can be more than just simple values like 31; they can be any valid expression, even a function call:


function foo(x = y + 3, z = bar( x )) {
    console.log( x, z );
}

Destructuring

Simply put: structured assignments
ES5:


function foo() {
  return [1,2,3];
}

var tmp = foo(),
  a = tmp[0], b = tmp[1], c = tmp[2];

console.log( a, b, c ); // 1 2 3

ES6:


var [ a, b, c ] = foo();

Same is true about Orrays
ES5:


function bar() {
  return {
      x: 4,
      y: 5,
      z: 6
  };
}

var tmp = bar(),
  x = tmp.x, y = tmp.y, z = tmp.z;

console.log( x, y, z ); // 4 5 6

ES6:


var { x: x, y: y, z: z } = bar();

Object Property Assignment Pattern

If the property name being matched is the same as the variable you want to declare, you can actually shorten the syntax:


var { x, y, z } = bar();

console.log( x, y, z ); // 4 5 6

This is great. But when you need to assign a prop name to a different variable name, you can use the verbose syntax:


var { x: bam, y: baz, z: bap } = bar();

console.log( bam, baz, bap ); // 4 5 6
console.log( x, y, z ); // ReferenceError

In fact, the assignment expressions (a, y, etc.) don't actually need to be just variable identifiers. Anything that's a valid assignment expression is allowed. For example:


var o = {};

[o.a, o.b, o.c] = foo();
( { x: o.x, y: o.y, z: o.z } = bar() );

console.log( o.a, o.b, o.c ); // 1 2 3
console.log( o.x, o.y, o.z ); // 4 5 6

Object Property Assignment Pattern

If the property name being matched is the same as the variable you want to declare, you can actually shorten the syntax:


var { x, y, z } = bar();

console.log( x, y, z ); // 4 5 6

This is great. But when you need to assign a prop name to a different variable name, you can use the verbose syntax:


var { x: bam, y: baz, z: bap } = bar();

console.log( bam, baz, bap ); // 4 5 6
console.log( x, y, z ); // ReferenceError

Default Value Assignment

Both forms of destructuring can offer a default value option for an assignment, using the = syntax similar to the default function argument values discussed earlier.


var [ a = 3, b = 6, c = 9, d = 12 ] = foo();
var { x = 5, y = 10, z = 15, w = 20 } = bar();

console.log( a, b, c, d ); // 1 2 3 12
console.log( x, y, z, w ); // 4 5 6 20

Destructuring Parameters

In the following ES5 snippet, 42 is implicitly assigned to `x`


function foo(x) {
    console.log( x );
}

foo( 42 );

Which means that we should be able to destructure that assignment. Which we can!
ES6:


function foo( [ x, y ] ) {
    console.log( x, y );
}

foo( [ 1, 2 ] ); // 1 2
foo( [ 1 ] ); // 1 undefined
foo( [] ); // undefined undefined

Object destructuring works too:


function foo( { x, y } ) {
    console.log( x, y );
}

foo( { y: 1, x: 2 } ); // 2 1
foo( { y: 42 } ); // undefined 42
foo( {} ); // undefined undefined

Object Literal Extensions

ES6 adds a number of important convenience extensions to the humble { .. } object literal.
ES5:


var x = 2, y = 3,
	o = {
	    x: x,
	    y: y
	};

Consice props in ES6:


var x = 2, y = 3,
  o = {
      x,
      y
  };

Same is true with methods
ES5:


var o = {
    x: function(){
        // ..
    },
    y: function(){
        // ..
    }
}

Consice methods in ES6:


var o = {
    x() {
        // ..
    },
    y() {
        // ..
    }
}

Setting [[Prototype]]

This has been a non-standard feature of many JS engines, which in ES6 is now standard.


var o1 = {
    // ..
};

var o2 = {
    __proto__: o1,
    // ..
};

ES6 also offers a utility for this:


Object.setPrototypeOf( o2, o1 );

Object super

super is typically thought of as being only related to classes. However, due to JS's classless-objects-with-prototypes nature, super is equally effective, and nearly the same in behavior, with plain objects' concise methods.


var o1 = {
    foo() {
        console.log( "o1:foo" );
    }
};

var o2 = {
    foo() {
        super.foo();
        console.log( "o2:foo" );
    }
};

Object.setPrototypeOf( o2, o1 );

o2.foo();       // o1:foo o2:foo

Template Literals

ES5:


var name = "Kyle";

var greeting = "Hello " + name + "!";

console.log( greeting ); // "Hello Kyle!"
console.log( typeof greeting ); // "string"

ES6:


var name = "Kyle";

var greeting = `Hello ${name}!`;

console.log( greeting );            // "Hello Kyle!"
console.log( typeof greeting );     // "string"

Backtick delimiter also allows for multi-line text:
ES5:


var text = 'Now is the time for all good men' +
	'to come to the aid of their' +
	'country!'; // Ugh

ES6:


var text =
`Now is the time for all good men
to come to the aid of their
country!`; // Yay

This makes interpolated expressions happen too!


function upper(s) {
    return s.toUpperCase();
}

var who = "reader";

var text =
`A very ${upper( "warm" )} welcome
to all of you ${upper( `${who}s` )}!`;

console.log( text );
// A very WARM welcome
// to all of you READERS!

Arrow Functions

Let's first illustrate what an arrow function looks like, as compared to normal functions:


function foo(x,y) {
    return x + y;
}

// versus

var foo = (x,y) => x + y; // Implicit return

Other usecases:


var f1 = () => 12;
var f2 = x => x * 2;
var f3 = (x,y) => {
    var z = x * 2 + y;
    y++;
    x *= 3;
    return (x + y + z) / 2;
};

Arrow functions for functional code:


var a = [1,2,3,4,5];

a = a.map( v => v * 2 );

console.log( a ); // [2,4,6,8,10]

Arrow functions save the scope!
ES5:


var controller = {
    makeRequest: function(..){
        var self = this;

        btn.addEventListener( "click", function(){
            // ..
            self.makeRequest(..);
        }, false );
    }
};

ES6:


var controller = {
    makeRequest: function(..){
        btn.addEventListener( "click", () => {
            // ..
            this.makeRequest(..);
        }, false );
    }
};

Lexical this in the arrow function callback in the previous snippet now points to the same value as in the enclosing makeRequest(..) function. In other words, => is a syntactic stand-in for var self = this.

for..of Loops

Joining the for and for..in loops from the JavaScript we're all familiar with, ES6 adds a for..of loop, which loops over the set of values produced by an iterator.


var a = ["a","b","c","d","e"];

for (var idx in a) {
    console.log( idx );
}
// 0 1 2 3 4

for (var val of a) {
    console.log( val );
}
// "a" "b" "c" "d" "e"

in ES5, we would have to do the following:


var a = ["a","b","c","d","e"],
    k = Object.keys( a );

for (var val, i = 0; i < k.length; i++) {
    val = a[ k[i] ];
    console.log( val );
}
// "a" "b" "c" "d" "e"

Symbols

A new primitive type has been added to JavaScript: the symbol.


// create a symbol
var sym = Symbol( "some optional description" );

typeof sym;     // "symbol"

Some things to note:

  • A symbol is a unique and immutable data type and may be used as an identifier for object properties.
  • You cannot and should not use new with Symbol(..). It's not a constructor, nor are you producing an object.
  • The parameter passed to Symbol(..) is optional. If passed, it should be a string that gives a friendly description for the symbol's purpose.
  • The typeof output is a new value ("symbol") that is the primary way to identify a symbol.
  • You can use symbols as identifiers when adding properties to an object.

Using symbols as keys is where symbols become interesting. Because symbols are unique, a symbol can create a unique property inside an object. You never have to worry about conflicting with existing methods, or being accidently overwritten.


let firstName = Symbol();

let person = {
    lastName: "Allen",
    [firstName]: "Scott",
};

let names = [];
for(var p in person) {
    names.push(p);
}

expect(names.length).toBe(1); // Symbol keys are ignored in all iterables
expect(names[0]).toBe("lastName");

"Well Known" Symbols are those that come built-in with ES6 like:

  • Symbol.iterator

[1,2,3][Symbol.iterator]; // [Function: values]

2. Organization

Iterators

Iterators are a way of organizing ordered, sequential, pull-based consumption of data.
An iterator is a structured pattern for pulling information from a source in one-at-a-time fashion.

Examples:

  • A utility that produces a new unique identifier each time it's requested
  • Attach an iterator to a database query result to pull out new rows one at a time.

Many of the built-in data structures in JavaScript will now expose an iterator implementing this standard. And you can also construct your own iterators adhering to the same standard, for maximal interoperability.

Interface:


Iterator [required]
    next() {method}: retrieves next IteratorResult

There are two optional members that some iterators are extended with:


Iterator [optional]
    return() {method}: stops iterator and returns IteratorResult
    throw() {method}: signals error and returns IteratorResult

The IteratorResult interface is specified as:


IteratorResult
    value {property}: current iteration value or final return value
        (optional if `undefined`)
    done {property}: boolean, indicates completion status

Example Usecase:


var arr = [1,2,3];

var it = arr[Symbol.iterator]();

it.next(); // { value: 1, done: false }
it.next(); // { value: 2, done: false }
it.next(); // { value: 3, done: false }

it.next(); // { value: undefined, done: true }

var greeting = "hello world";

var it = greeting[Symbol.iterator]();

it.next();      // { value: "h", done: false }
it.next();      // { value: "e", done: false }

Generators

Functions that do not run to completion. `yield` is used to incrementally execute the function.


function *foo() {
    yield 1;
    yield 2;
    yield 3;
}

function *bar() {
    yield *foo();
}

Early completion:


function *foo() {
    yield 1;
    yield 2;
    yield 3;
}

var it = foo();

it.next();              // { value: 1, done: false }

it.return( 42 );        // { value: 42, done: true }

it.next();              // { value: undefined, done: true }

Async application of generators:


function request(url) {
    // `it.next(..)` is the generator's iterator-resume call
    makeAjaxCall( url, function(response){
        it.next( response );
    } );
    // Note: nothing returned here!
}

function *main() {
    var result1 = yield request( "http://some.url.1" );
    var data = JSON.parse( result1 );

    var result2 = yield request( "http://some.url.2?id=" + data.id );
    var resp = JSON.parse( result2 );
    console.log( "The value you asked for: " + resp.value );
}

var it = main();
it.next(); // get it all started

Async application of generators:


function request(url) {
    // `it.next(..)` is the generator's iterator-resume call
    makeAjaxCall( url, function(response){
        it.next( response );
    } );
    // Note: nothing returned here!
}

function *main() {
    var result1 = yield request( "http://some.url.1" );
    var data = JSON.parse( result1 );

    var result2 = yield request( "http://some.url.2?id=" + data.id );
    var resp = JSON.parse( result2 );
    console.log( "The value you asked for: " + resp.value );
}

var it = main();
it.next(); // get it all started

Modules

1991 called. they wanted their language feature back.
The Old Way:


function Hello(name) {
    function greeting() {
        console.log( "Hello " + name + "!" );
    }

    // public API
    return {
        greeting: greeting
    };
}

var me = Hello( "Kyle" );
me.greeting();          // Hello Kyle!

CommonJS: (NodeJS Style)


// salute.js
var MySalute = "Hello";
module.exports = MySalute;

// main.js
var salute = require('./salute.js');

ES6 Modules - Export


export function foo() {
    // ..
}

export var awesome = 42;

var bar = [1,2,3];
export { bar };

//or
function foo(..) {
    // ..
}

export default foo;

//or
function foo(..) {
    // ..
}
export { foo as default };

//or
export default function foo(..) {
    // ..
}

ES6 Modules - Import


import { foo, bar, baz } from "foo";
//or
import { foo } from "foo";
//or
import { foo as theFooFunc } from "foo";
// or
import "foo";

Classes

Syntactic sugar for Prototypcal Inheritence
ES5:


function Foo(a,b) {
    this.x = a;
    this.y = b;
}

Foo.prototype.gimmeXY = function() {
    return this.x * this.y;
}

ES6:


class Foo {
    constructor(a,b) {
        this.x = a;
        this.y = b;
    }

    gimmeXY() {
        return this.x * this.y;
    }
}

Extend and Super


class Bar extends Foo {
    constructor(a,b,c) {
        super( a, b );
        this.z = c;
    }

    gimmeXYZ() {
        return super.gimmeXY() * this.z;
    }
}

var b = new Bar( 5, 15, 25 );

b.x;                        // 5
b.y;                        // 15
b.z;                        // 25
b.gimmeXYZ();               // 1875

`static`


class Foo {
    static cool() { console.log( "cool" ); }
    wow() { console.log( "wow" ); }
}

class Bar extends Foo {
    static awesome() {
        super.cool();
        console.log( "awesome" );
    }
    neat() {
        super.wow();
        console.log( "neat" );
    }
}
Foo.cool();                 // "cool"
Bar.cool();                 // "cool"
Bar.awesome();              // "cool          // "awesome"
var b = new Bar();
b.neat();                   // "wow 					// "neat"
b.awesome;                  // undefined
b.cool;                     // undefined

3. Async Flow Control

Promises are not about replacing callbacks. Promises provide a trustable intermediary -- that is, between your calling code and the async code that will perform the task -- to manage callbacks. Promises can be chained together, which can sequence a series of asychronously completing steps. Together with higher-level abstractions like the all(..) method (in classic terms, a "gate") and the race(..) method (in classic terms, a "latch"), promise chains provide a mechanism for async flow control.

Callback Style:


function ajax(url,cb) {
    // make request, eventually call `cb(..)`
}

// ..

ajax( "http://some.url.1", function handler(err,contents){
    if (err) {
        // handle ajax error
    }
    else {
        // handle `contents` success
    }
} );

Promise:


ajax( "http://some.url.1" )
.then(
    function fulfilled(contents){
        return ajax(
            "http://some.url.2?v=" + contents
        );
    },
    function rejected(reason){
        return ajax(
            "http://backup.url.3?err=" + reason
        );
    }
)
.then( function fulfilled(contents){
    // `contents` comes from the subsequent
    // `ajax(..)` call, whichever it was
} );

Promise:


function updatePriority(abTest) {
  ABTestService
    .get({ abTestId: abTest.ab_test_id })
    .$promise
    .then(function(response) {
      return response.item;
    })
    .then(function(resource) {
      return ABTestService.update({ abTestId: resource.abTest.abTestId }, resource);
    })
    .then(function(response) {
      $log.log('Priority Updated ', response);
    })
    .catch(function(err) {
      $log.error(err);
    });
}

4. Collections

Maps

In JS, we use Objects to create objects-as-maps. The drawback is the inability to use a non-string value as the key.


let map = new Map();

map.set('foo', 123);
map.get('foo');
map.set(NaN, 123);

TypedArrays


var buf = new ArrayBuffer( 32 );
buf.byteLength;                         // 32

buf is now a binary buffer that is 32-bytes long (256-bits), that's pre-initialized to all 0s. A buffer by itself doesn't really allow you any interaction exception for checking its byteLength property.
Several web platform features use or return array buffers, such as FileReader#readAsArrayBuffer(..), XMLHttpRequest#send(..), and ImageData (canvas data).

Thank you!