ECMAScript 6 => `${pure}awesomness` – A brief history of JavaScript – ES6 finally



ECMAScript 6 => `${pure}awesomness` – A brief history of JavaScript – ES6 finally

0 4


cool-stuff-coming-es6

A quick overview of new cool features that are coming in the next version of ECMAScript

On Github qmmr / cool-stuff-coming-es6

ECMAScript 6 => `${pure}awesomness`

About me:

A brief history of JavaScript

  • created in 10 days in May 1995 by Brendan Eich, an engineer at Netscape
  • first released with Netscape 2 early in 1996
  • originally called Mocha, renamed to LiveScript to reflect its dynamic nature
  • finally renamed to JavaScript after the popular Sun Microsystem's Java language

Mocha JavaScript flavors

  • Microsoft released a mostly-compatible version of the language called JScript with IE 3 several months later
  • ECMAScript standard in 1997
  • The standard received a significant update as ECMAScript edition 3 in 1999
  • The fourth edition was abandoned.
  • ECMAScript edition 5, published in December of 2009
  • ECMAScript 6 (ES6 Harmony) - work in progress
- LiveScript was renamed to JavaScript (because of popularity of Java) edespite the two having very little in common. This has been a source of confusion ever since. - Netscape submitted the language to European Computer Manufacturers Association which resulted in the first edition of the ECMAScript standard in 1997. - ECMAScript edition 3 in 1999 and has stayed pretty much stable ever since - The fourth edition was abandoned, due to political differences concerning language complexity. - Many parts of the fourth edition formed a basis of the new ECMAScript edition 5, published in December of 2009

ES6 finally

Let's dive in!

Almost there...

Available resources:

What's coming?

Syntax refinements:

New syntax:

Organizational features:

  • classes
  • modules

New standard library features:

  • set
  • map
  • weakmap + weakset
  • promises
  • proxies

...rest

  • template strings // template literals `string text` // Multiline strings `string text line 1 string text line 2` // String interpolation `string text ${expression} string text` var greet = function(greet = 'Hello', name = 'stranger') { return `${ greet } ${ name }, how are you today?`; }; console.log(greet()); "Hello stranger, how are you today?" console.log(greet('Hi', 'Marcin')); "Hi Marcin, how are you today?" --- var lovem = function(strings, ...vars) { return `${ vars[0] } <3 ${ strings[1] }!`; }; var user = 'Marcin'; // syntax - tagged template console.log(lovem `${ user }ECMAScript2015`);
  • unicode
  • module loaders
  • symbols
  • subclassable built-ins
  • math + number + string + object APIs
  • binary and octal literals
  • reflect api
  • tail calls

Part one

Syntax refinements

=> (arrow)

  • shorter syntax for common case use of anonymous functions
  • retains lexical scope of this

shorter syntax for common case use of anonymous functions

old way

var reflect = function(value) {
    return value;
};

var reflect = value => value;

or

var reflect = (value) => {
    return value;
}

---

var sum = function(a, b) {
    return a + b;
};

var sum = (a, b) => a + b;

// OR

var sum = (a, b) => {
    return a + b;
};

---

var getName = function() {
    return 'Marcin';
};

var getName = () => 'Marcin';

---

var square = function(n) {
    return n * n;
};
var numbers = [ 0, 1, 2, 3, 4, 5 ];
var doubles = numbers.map( square ); // [ 0, 1, 4, 9, 16, 25 ]

var numbers = [ 0, 1, 2, 3, 4, 5 ];
var doubles = numbers.map( n => n * n ); // [ 0, 1, 4, 9, 16, 25 ]
                        

new way

let people = [
    {
        name: 'Joe',
        lastName: 'Doe',
        occupation: 'Web Developer',
        languages: [ 'HTML', 'CSS', 'JavaScript' ]
    },
    {
        name: 'Jane',
        lastName: 'Doe',
        occupation: 'Back-end Developer',
        languages: [ 'PHP', 'Ruby', 'Python', 'JavaScript' ]
    }
]

                        

retains lexical scope of this

old way

var person = {
    name: 'Joe',
    friends: [ 'Jane', 'Johnny', 'Luke' ],
    getName: function() {
        setTimeout(function() {
            console.log(this.name);
        }, 1000);
    },
    showFriends: function() {
        return this.friends.map(function(friend) {
            return `${ this.name } is friend with ${ friend }`;
        });
    }
};

var person = {
    name: 'Joe',
    friends: [ 'Jane', 'Johnny', 'Luke' ],
    getName: function() {
        var that = this;
        setTimeout(function() {
            console.log(that.name);
        }, 1000);
    },
    showFriends: function() {
        var self = this;
        return this.friends.map(function(friend) {
            return `${ self.name } is friend with ${ friend }`;
        });
    }
};

var person = {
    name: 'Joe',
    friends: [ 'Jane', 'Johnny', 'Luke' ],
    getName: function() {
        setTimeout(function() {
            console.log(this.name);
        }.bind(this), 1000);
    },
    showFriends: function() {
        return this.friends.map(function(friend) {
            return `${ this.name } is friend with ${ friend }`;
        }, this);
    }
};

var person = {
    name: 'Joe',
    friends: [ 'Jane', 'Johnny', 'Luke' ],
    getName() {
        setTimeout(() => console.log(this.name), 1000);
    },
    showFriends() {
        return this.friends.map(friend => `${ this.name } is friend with ${ friend }`)
    }
};
console.log(person.getName())
console.log(person.showFriends())
                        

new way

let person = {
    name: 'Joe',
    friends: [ 'Jane', 'Johnny', 'Luke' ],
    getName() {
        setTimeout(() => console.log( 'Hi, my name is ' + this.name ), 1000);
    },
    showFriends() {
        this.friends.forEach( friend => console.log( this.name + ' is friend with ' + friend ) );
    }
};
person.getName();
person.showFriends();
                        
es6fiddle

Refined object literals

object literals

old way

var teacher = 'Michael'
var student = {
    name: 'Joe',
    teacher: teacher,
    greet: function () {
        return 'Hi, ' + this.name + '!'
    }
}
student[ '_id_' + +new Date() ] = Math.floor( Math.random() * 10000000 )
                            

object literals

new way

                                
// ES5
var title = 'ECMAScript 2015';
var students = [ 'Joe', 'Jane', 'Phil' ];
var id = function() { return 42 };
var course = {
    title: title,
    students: students,
    welcome: function() {
        return 'Welcome to ' + this.title + ' course!';
    }
};
course[ '_prop_' + id() ] = id();

// ES6
var title = 'ECMAScript 2015';
var students = [ 'Joe', 'Jane', 'Phil' ];
var id = function() { return 42 };
var course = {
    title,
    students,
    welcome() {
        return `Welcome to ${ this.title } course!`;
    },
    [ `_prop_${ id() }` ]: id() // ['_prop42']: 42
};

for (let key of Object.keys(course)) {
  console.log(`Course.${ key } = ${ course[key] }`)
}

                                
                            

New syntax

let

is the new var

scoped to the block rather then function

does not have hoisting characteristics

block being anything inside curly braces - loops - if/else/else if - switch - try/catch*

scoped inside if clause


// PROMISES

// jQuery style
$.ajax('/some/url', options)
    .done(function() {
        alert('success');
    })
    .fail(function() {
        alert('error');
    });

---

function timeout(duration = 1000) {
    return new Promise(function(resolve, reject) {
        setTimeout(resolve, duration);
    });
}

timeout(1000).then(function() {
    console.log('1000ms have passed!');
});

---

// Promise.all()

function timeout(duration = 1000) {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve(`${ duration }ms have passed!`);
        }, duration);
    });
}

Promise.all([ timeout(), timeout(1500), timeout(2000) ])
    .then(function(value) {
        console.log(value);
    });

---

function httpGet(url) {
    return new Promise(function(resolve, reject) {
        var request = new XMLHttpRequest();
        request.onreadystatechange = function() {
            if (this.status === 200) {
                resolve(this.response); // Success
            } 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();
    });
}

---

httpGet('http://example.com/file.txt')
    .then(
        function(value) {
            // fullfilment
            console.log('Contents: ' + value);
        },
        function(err) {
            // rejection
            console.error('Something went wrong', err);
        }
    );

---

httpGet('http://example.com/file.txt')
    .then(function(value) {
        // fullfilment
        console.log('Contents: ' + value);
    });

---

httpGet('http://example.com/file.txt')
    .then(
        null,
        function(err) {
            // rejection
            console.error('Something went wrong', err);
        }
    );

---

httpGet('http://example.com/file.txt')
    .catch(function(err) {
        // rejection
        console.error('Something went wrong', err);
    });

---

function timeout(duration = 1000, promise) {
    return new Promise(function(resolve, reject) {
        promise.then(resolve);
        setTimeout(function() {
            reject(new Error('Timeout after ' + duration + ' ms.'));
        }, duration);
    });
}

timeout(5000, httpGet('http://example.com/file.txt'))
    .then(function(value) {
        console.log('Contents: ' + value);
    })
    .catch(function(reason) {
        console.error('Error or timeout', reason);
    });

---




function printName ( language ) {
    var name = 'Marcin';
    if ( language === 'English' ) {
        let name = 'Martin';
        console.log( name ); // Martin
    }
    console.log( name ); // Marcin
}

---

function func() {
    let foo = 5;
    if (1) {
        let foo = 10; // shadows outer `foo`
        console.log(foo); // 10
    }
    console.log(foo); // 5
}

---

if (true) { // enter new scope, TDZ starts
    // Uninitialized binding for `tmp` is created

    console.log(typeof tmp); // ReferenceError
    tmp = 'abc'; // ReferenceError
    console.log(tmp); // ReferenceError

    let tmp; // TDZ ends, `tmp` is initialized with `undefined`
    console.log(tmp); // undefined

    tmp = 123;
    console.log(tmp); // 123
}
                        
you cannot redeclare identifier, throws syntax error

Local scopes

common problem

function wrapper ( arr ) {
    var result = [], i, len;
    for ( i = 0, len = arr.length; i < len; ++i ) {
        result[i] = function () {
            return arr[i];
        }
    }
    return result
}
var wrapped = wrapper([ 'Luke', 'Obi-Wan', 'Yoda' ]);
console.log( wrapped[1]() ); // undefined

---

let arr = [];
for (var i=0; i < 3; i++) {
    arr.push(function() {
        return i;
    });
}
arr.map(function(x) {
    return x();
}); // [ 3, 3, 3 ]
                        

ಠ_ಠ

Old solution

function wrapper ( arr ) {
    var result = [], i, len;
    for ( i = 0, len = arr.length; i < len; ++i ) {
        (function ( j ) {
            result[i] = function () { return arr[j] }
        })( i )
    }
    return result;
}
var wrapped = wrapper([ 'Luke', 'Obi-Wan', 'Yoda' ]);
console.log( wrapped[2]() ); // 'Yoda'
                        

ಠಿ_ಠ

New solution

function wrapper ( arr ) {
    var result = [];
    for ( let i = 0, len = arr.length; i < len; ++i ) {
        result[i] = function () { return arr[i] }
    }
    return result;
}
var wrapped = wrapper([ 'Luke', 'Obi-Wan', 'Yoda' ]);
console.log( wrapped[1]() ); // 'Obi-Wan'
                        

\(`0´)/

const

similar to let, block scoped

- block being anything inside curly braces - loops - if/else/else if - switch - try/catch*

once declared, cannot be redeclared

const FOO = 'hello world';
FOO = 'Goodbye, world!'; // error
                        

declaration must be followed by assignment

const FOO;
console.log( FOO ); // undefined
FOO = 'hello world!'; // error: cannot reasign const
                        
- otherwise we have undefined value, that we cannot change (sic!) - IE will give an error, others will ignore it (Firefox)

const can hold any type of variables (array, object etc.)

const FOO = [];
FOO.push( 'Hello' );
FOO.push( 'world!' );
console.log(FOO.join( ', ' )); // Hello, world!
                        
const BAR = {};
BAR.baz = 'Hello, world!';
console.log( BAR.baz ); // hello, world!

--

Object.assign()

var alias = 'Spider-man';
var person = {
    firstName: 'Peter',
    lastName: 'Parker'
};
var superpowers = [ 'web', 'wall-climbing' ];
var peter = Object.assign({}, person, { alias: alias }, { superpowers: superpowers });

console.log(JSON.stringify(peter));
/*
{
    "alias": "Spider-man",
    "firstName": "Peter",
    "lastName": "Parker",
    "superpowers": [ "web", "wall-climbing" ]
}
*/

---

var alias = 'Spider-man';
var person = {
    firstName: 'Peter',
    lastName: 'Parker'
};
var superpowers = [ 'web', 'wall-climbing' ];
var peter = Object.assign({}, person, { alias, superpowers });

console.log(JSON.stringify(peter));
/*
{
    "alias": "Spider-man",
    "firstName": "Peter",
    "lastName": "Parker",
    "superpowers": [ "web", "wall-climbing" ]
}
*/
                        

default parameters

ʘ‿ʘ

checking for optional params

old way
function createCustomElement (tagName, id, parent) {
    if (!id) {
        id = generateID();
    }
    if (!parent) {
        parent = document.body;
    }
    var element = document.createElement( tagName );
    element.id = id;
    parent.appendChild(element);
    return element;
}

function makeRequest(url, timeout, callback) {
    timeout = timeout || 2000;
    callback = callback || function() {};

    // the rest of the function
}

function makeRequest(url, timeout = 2000, callback = function() {}) {
    // the rest of the function
}

---

function timeout(timeout = 2000, callback = function() {}) {
    // the rest of the function
}

---

function getCallback() {
    return function() {};
}

function timeout(timeout = 2000, getCallback()) {
    // the rest of the function
}

---

// uses default timeout and callback
makeRequest('/foo');

// uses default callback
makeRequest('/foo', 500);

// doesn't use defaults
makeRequest('/foo', 500, function(el) {
    doSomething(el);
});

var getCallback = function() {
    return function callback() {}
};

function makeRequest(url, timeout = 2000, callback = getCallback()) {
    // the rest of the function
}
                        

assignment inside parenthesis

new way
function createCustomElement (tagName = 'div', id = generateID(), parent = document.body) {
    let element = document.createElement(tagName);
    element.id = id;
    parent.appendChild( element );
    return element;
}
                        

...rest

is an array of params that were not declared

solves the arguments problem

\(`0´)/

rest params

old way
var sum = function(a, b) {
   return a + b;
}
sum(1, 2); // 3

var sum = function(a, b) {
   return a + b;
}
sum(1, 2, 3); // ???

var sum = function() {
    var args = [].slice.call(arguments);
    return args.reduce(function(accu, curr) {
        return accu + curr;
    }, 0);
}
sum(1, 2, 3, 4, 5) // 15
                        
sum function with 2 required params ( a, b ) any additional params are optional

rest params

new way
var sum = function(...rest) {
    return rest.reduce(function(prev, current) {
        return prev + current;
    }, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15

function sum(...rest) {
    return rest.reduce((prev, current) => prev + current, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
                        

...spread

expands array of args into individual variables

getting the highest value

old way
var numbers = [ 0, 20, 22, 42 ];
Math.max.apply(null, numbers); // 42

function sum(x, y, z) {
    return x + y + z;
}
sum(numbers[0], numbers[1], numbers[2]); // 42
                        
new way
var numbers = [ 0, 20, 22, 42 ];
Math.max(...numbers); // 42

function sum(x, y, z) {
    return x + y + z;
}
sum(...numbers); // 42

                        

spread array

old way
var getCoords = function(x, y, z) {
    return {
        x: x,
        y: y,
        z: z
    };
}
var points = [ 100, 50, 42 ];
var coords = getCoords(points[0], points[1], points[2]); // { "x": 100, "y": 50, "z": 42 }
                        

spread array

new way
var getCoords = function(x, y, z) {
    return {
        x: x,
        y: y,
        z: z
    };
}
var points = [ 100, 50, 42 ];
var coords = getCoords(...points); // { "x": 100, "y": 50, "z": 42 }

var getCoords = function(x, y, z) {
    return { x, y, z };
}
var points = [ 100, 50, 42 ];
var coords = getCoords(...points); // { "x": 100, "y": 50, "z": 42 }

var getCoords = (x, y, z) => ({ x, y, z });
var points = [ 100, 50, 42 ];
var coords = getCoords(...points); // { "x": 100, "y": 50, "z": 42 }

var getPoints = ({ x, y, z }) => [ x, y, z];
var coords = getCoords(...getPoints({ "x": 100, "y": 50, "z": 42 })); // { "x": 100, "y": 50, "z": 42 }
                        
- notice spread into new object literal syntax

joining arrays

old way
var primes = [ 2, 3, 5 ]
var fibonacci = [ 0, 1, 1 ]
fibonacci = fibonacci.concat( primes ) // [0, 1, 1, 2, 3, 5]
                        
new way
let fibonacci = [ 0, 1, 1, ...primes ]; // [0, 1, 1, 2, 3, 5]
                            

joining arrays - advanced

old way
var primes = [ 2, 3, 5 ];
var fibonacci = [ 0, 1, 1, 8, 13, 21 ];
primes.forEach(function ( num ) {
    var index = fibonacci.indexOf( 8 );
    fibonacci.splice( index, 0, num );
})
console.log( fibonacci ); // [0, 1, 1, 2, 3, 5, 8, 13, 21]
                        
new way
let fibonacci = [ 0, 1, 1, ...primes, 8, 13, 21 ];
console.log( fibonacci ); // [0, 1, 1, 2, 3, 5, 8, 13, 21]
                            

The rest of new syntax features in part two

Part two

New syntax continues

destructuring

extract data from objects and arrays

swaping values of variables

old way
var a = 1;
var b = 2;
var tmp = a;
a = b;
b = tmp;
                        

ಠ_ಠ

swaping values of variables

new way
let a = 1;
let b = 2;
let tmp = a;
a = b;
b = tmp;

let [ a, b ] = [ 1, 2 ];
[ a, b ] = [ b, a ];
console.log(a, b); // 2, 1
                        

ʘ‿ʘ

extract array

#008744     (0,135,68) // green
#0057e7     (0,87,231) // blue
#d62d20     (214,45,32) // red
#ffa700     (255,167,0) // orange
#ffffff     (255,255,255) // white

let getColors = function() {
    return [ '#008744', '#0057e7', '#d62d20' ];
};
let colors = getColors();
let green = colors[0];
let blue = colors[1];
let red = colors[2];

let [ green, blue, red ] = getColors;
console.log(green, blue, red); // "#008744" "#0057e7" "#d62d20"

---

let getColors = function() {
    return [ '#008744', '#0057e7', '#d62d20' ];
};
let colors = getColors();
let green = colors.shift();

console.log(green, colors); // "#008744", [ "#0057e7", "#d62d20" ]

---

let getColors = function() {
    return [ '#008744', '#0057e7', '#d62d20' ];
};
let [ green, ...colors ] = getColors();

console.log(green, colors); // "#008744", [ "#0057e7", "#d62d20" ]

---

let getColors = function() {
    return [
        [ '#008744', '0,135,68' ],
        [ '#0057e7', '0,87,231' ]
    ];
}
let [
    [ green = '#00ff00' ] = [],
    [ blueHEX, blueRGB ] = [],
    [ , redRGB = '214,45,32' ] = []
] = getColors();
console.log(green, blueHEX, blueRGB, redRGB);

---

let getColors = function() {
    return [
        [ '#008744', '0,135,68' ],
        [ '#0057e7', '0,87,231' ],
        [ '#d62d20', '214,45,32' ]
    ];
}

let colors = getColors();
let green = colors[0][0];
let blue = colors[1][0];
let red = colors[2][0];

let [ green, ...colors ] = getColors; // "#008744", [ "#0057e7","#d62d20" ]

---

let [ [ green ], [ blue ], [ red ] ] = colors;
console.log(green, blue, red); // "#008744" "#0057e7" "#d62d20"

---

let destruct = function(str) {
    return str.split(/\n/);
};
let headline = 'Congratulations!\nYou have been rewarded $50’;
var [ title, message ] = destruct(headline);
console.log(title); // "Congratulations!"
console.log(message); // "You have been rewarded $50!"

let destruct = str => str.split(/\n/);
let headline = 'Congratulations!\nYou have been rewarded $50’;
let [ title, message ] = destruct(headline);
console.log(title); // "Congratulations!"
console.log(message); // "You have been rewarded $50!"
                        

omitting certain values

let destruct = ( str ) => str.split( /\s/ )
let [ first, , , , last ] = destruct( 'Joe James John Jake Josh' );
console.log( first ); // 'Joe'
console.log( last ); // 'Josh'
console.log( second ); // ReferenceError: second is not defined
                            
omitting - second code block

destructuring objects

long way / control naming
let person = {
    firstName: 'Joe',
    lastName: 'Doe'
};
let { firstName: first, lastName: last } = person;
console.log(first); // "Joe"
console.log(last); // "Doe"

let person = {
    firstName: 'Joe',
    lastName: 'Doe'
};
let { firstName, lastName } = person;
console.log(firstName); // "Joe"
console.log(lastName); // "Doe"

---

let person = {
    firstName: 'Joe',
    lastName: 'Doe',
    email: {
        personal: 'joe.doe@gmail.com',
        work: 'joe.doe@gamesys.co.uk'
    }
};
let {
    firstName,
    lastName,
    email: { personal: email }
} = person;
console.log(firstName); // "Joe"
console.log(lastName); // "Doe"
console.log(email); // "joe.doe@gmail.com"

---

let person = {
    firstName: 'Joe',
    lastName: 'Doe',
    emails: [ 'joe.doe@gmail.com', 'joe.doe@gamesys.co.uk' ]
};
let { firstName, lastName, emails: [ email ] } = person;
console.log(firstName); // "Joe"
console.log(lastName); // "Doe"
console.log(email); // "joe.doe@gmail.com"

---

let person = {
    firstName: 'Joe',
    lastName: 'Doe',
    emails: [ 'joe.doe@gmail.com', 'joe.doe@gamesys.co.uk' ]
};
let { firstName, lastName, emails: [ personalEmail, workEmail ] } = person;
console.log(firstName); // "Joe"
console.log(lastName); // "Doe"
console.log(personalEmail); // "joe.doe@gmail.com"
console.log(workEmail); // "joe.doe@gamesys.co.uk"


---

let person = {
    firstName: 'Joe',
    lastName: 'Doe',
    emails: [ 'joe.doe@gmail.com', 'joe.doe@gamesys.co.uk' ]
};
let { firstName, lastName, emails: [ personalEmail, workEmail ] } = person;
console.log(firstName); // "Joe"
console.log(lastName); // "Doe"
console.log(personalEmail); // "joe.doe@gmail.com"
console.log(workEmail); // "joe.doe@gamesys.co.uk"


---


function getFullname({ firstName, lastName }) {
    return `${ lastName }, ${ firstName }`;
}
getFullname(person); // "Doe, Joe"
                        

destructuring objects

short way / same variables names
let person = {
    firstName: 'John',
    lastName: 'Doe'
};
let { firstName, lastName } = person;
console.log(firstName, lastName); // John Doe
                        

destructuring function parameters

let person = {
    age: 30,
    firstName: 'John',
    lastName: 'Doe',
    email: {
        work: 'john.doe@office.com',
        home: 'jdoe@gmail.com'
    }
};

function describePerson ({ firstName, lastName, email: { home: email }}) {
    console.log(`${firstName} ${lastName} | email: ${email}`);
}

describePerson( person ); // John Doe | email: jdoe@gmail.com
                        
you can destructure nested objects

for..of

iterates over values of array

for..in loop problem

var numbers = [ 1, 2, 3, 4, 5 ]
for (var num in numbers) {
    console.log( num )
}
// 0, 1, 2, 3, 4
                        

WTF?!?ಠ_ಠ

looping over array

old way
[ 1, 2, 3, 4, 5 ].forEach(function ( number ) {
    console.log( number )
})
// 1, 2, 3, 4, 5
                        

ಠಿ_ಠ

looping over array

new way
for ( let number of numbers ) {
    console.log( number )
}
// 1, 2, 3, 4, 5
                        

\(`0´)/

array-like objects

old way
  • one
  • two
  • three
var items = [].slice.call(document.getElementsByTagName('li'))
items.forEach(function ( el ) {
    console.log( el.textContent )
})

                        
new way
for ( let item of items ) {
    console.log( item.textContent )
}
                            

iterators

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

An iterable is an object which has an internal method, written in the current ES6 draft specs as obj[@@iterator](), that returns an iterator.

iterating over array

let numbers = [ 1, 2 ]
let iter = numbers["@@iterator"]()
iter.next() // { value: 1, done: false }
iter.next() // { value: 2, done: false }
iter.next() // { value: undefined, done: true }
                        
using iterable
for ( let num of iter ) { console.log( num ) }
                            
"@@iterator" is implemented in Firefox, ES6 Draft Spec defines Symbol.iterator

generators

ECMAScript 6 Draft

First-class coroutines, represented as objects encapsulating suspended execution contexts. Generators are functions that can suspend themselves (using the yield keyword) and be resumed (from the outside world) by calling their “next” method.

even numbers

generator example
function* evenGen ( arr ) {
    for ( let num of arr ) {
        if ( num % 2 === 0 ) {
            yield num;
        }
    }
}
let numbers = [ 1, 2, 3, 4, 5, 6 ];
for (let num of evenGen( numbers )) {
    console.log( num );
}
                        
function followed by * and yield keyword

Fibonacci sequence

old way
function fibonacci ( max ) {
    var a = 0, b = 1, c = 0;
    for ( var i = 0; i < max; ++i ) {
        console.log( a );
        c = a;
        a = b;
        b = c + b;
    }
}
fibonacci( 10 ); // 0 1 1 2 3 5 8 13 21 34
                        

Fibonacci sequence

new way
function* fibonacci () {
    var [ prev, curr ] = [ 0, 1 ];
    while ( 1 ) {
        yield prev;
        [ prev, curr ] = [ curr, prev + curr ];
    }
}
var gen = fibonacci();
//gen.next() // {value: 0, done: false}
var fibSeq = [];
for ( var n of gen) {
    if (n > 34) break;
    o.push(n);
}
console.log( fibSeq.join(', ') ) // 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
                        

generator and objects

function Person ( firstName, lastName ) {
    this.firstName = firstName;
    this.lastName = lastName;
}
Person.prototype.greet = function () {
    return 'Hi, my name is ' + this.firstName;
};
let johnDoe = new Person( 'John', 'Doe' );
for ( let val of johnDoe ) {
    console.log( val );
}
// TypeError: john['@@iterator'] is not a function
                        

generator and objects

function* objGen ( obj ) {
    for ( let prop in obj ) {
        if (obj.hasOwnProperty(prop) ) {
            yield [prop, obj[prop]]
        }
    }
}

for ( let [key, val] of objGen( johnDoe ) ) {
    console.log( key + ': ' + val )
}
// "firstName: John"
// "lastName: Doe"
                        

implementing generator into object

Person.prototype['@@iterator'] = function* () {
    for ( let prop in this ) {
        if (this.hasOwnProperty( prop )) {
            yield [ prop, this[prop] ]
        }
    }
}
for ( let [ key, val ] of johnDoe ) {
    console.log( key + ': ' + val )
}
// "firstName: John"
// "lastName: Doe"
                        

comprehensions

syntactic construct for creating arrays based on other arrays

Array map

old way
var jedis = [ 'Luke', 'Obi-wan', 'Yoda' ];
var jediMasters = jedis.map(function ( jedi ) {
    return 'master ' + jedi;
});
console.log( jediMasters ); // [ "master Luke", "master Obi-wan", "master Yoda" ]
                        

Array map

new way
let jedis = [ 'Luke', 'Obi-wan', 'Yoda' ];
let jediMasters = [ 'master ' + jedi for ( jedi of jedis ) ];
console.log( jediMasters ); // [ "master Luke", "master Obi-wan", "master Yoda" ]
                        

Array filter

old way
var jedis = [ 'Luke Skywalker', 'Yoda', 'Darth Vader', 'Darth Maul' ];
var jediKnights = jedis.filter(function ( jedi ) {
    return jedi.indexOf( 'Darth' ) == -1;
});
console.log( jediKnights ); // [ 'Luke Skywalker', 'Yoda' ]
                        

Array filter

new way
let jedis = [ 'Luke Skywalker', 'Yoda', 'Darth Vader', 'Darth Maul' ];
let jediKnights = [ knight for ( knight of jedis ) if ( knight.indexOf( 'Darth' ) == -1) ];
console.log( jediKnights ); // [ 'Luke Skywalker', 'Yoda' ]
                        

Generator comprehension

let jedis = [ 'Luke Skywalker', 'Yoda', 'Darth Vader', 'Darth Maul' ];
let jediKnights = ( knight for ( knight of jedis ) if ( knight.indexOf( 'Darth' ) == -1) );
console.log( jediKnights.next() ); // 'Luke Skywalker'
                        

Part three

Organizational features

  • Classes
  • Modules

classes

ES6 classes are a simple syntactic sugar of the prototype-based OO pattern

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

OOP

old way
function Human (firstName, lastName) {
    this.firstName = firstName || 'John';
    this.lastName = lastName || 'Doe';
}

Human.prototype.greet = function() {
    return 'Hi, my name is ' + this.firstName + ' ' + this.lastName;
};

// static method
Human.type = function() { return 'human' };
                    

OOP

new way
class Human {
    constructor(firstName = 'Joe', lastName = 'Doe') {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    greet() {
        return `Hi, my name is ${ this.firstName }.`;
    }

    static type() {
        return 'human';
    }
}

var joe = new Human();
console.log(joe.greet()); // 'Hi, my name is Joe.'
console.log(Human.type()); // 'human'
                    

OOP inheritance

old way
function SuperHuman (firstName, lastName, alias) {
    Human.call(this, firstName, lastName);
    this.alias = 'Spider-man' || 'unknown';
    this.superpowers = superpowers || [];
};

SuperHuman.prototype = Object.create( Human.prototype );
SuperHuman.prototype.constructor = SuperHuman;

SuperHuman.prototype.greet = function () { return  ( 'Hi, I am ' + this.alias + '!' ) };

SuperHuman.prototype.revealIdentity = function () {
    var greet = ( Human.prototype.greet.call(this) + ' a.k.a. ' + this.alias );
    return ( greet + ' and I am a ' + SuperHuman.type() + '.' );
};

SuperHuman.prototype.useSuperpower = function () {
    return ( this.alias + ' uses the ' + this.superpowers[0] + '!' )
};

SuperHuman.type = function () { return 'superhuman' };
                    

OOP inheritance

new way
class SuperHuman extends Human {
    constructor(firstName, lastName, alias = 'superhuman') {
        super(firstName, lastName);
        this.alias = alias;
    }
    greet() {
        return `Hi, I am ${ this.alias }!`;
    }
    revealIdentity() {
        return `Psst... It's me, ${ this.firstName }!`;
    }
    static type() {
        return 'superhuman';
    }
}
var spider = new SuperHuman('Peter', 'Parker', 'Spider-man');
console.log(spider.greet()); // Hi, I am Spider-man!
console.log(spider.revealIdentity()); // Psst... It's me, Peter!
                    

Getters and setters

old way
Object.defineProperty(Human.prototype, 'fullName', {
    get: function () {
        return ( this.lastName + ', ' + this.firstName );
    }
});

Object.defineProperty(Human.prototype, 'occupation', {
    get: function () {
        return ( 'Occupation: ' + this._occupation );
    },
    set: function ( occupation ) {
        return this._occupation = occupation;
    }
});
                    

Getters and setters

new way
class Person {
    constructor(firstName = 'Joe', lastName = 'Doe') {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    get fullName () {
        return `${this.lastName}, ${this.firstName}`
    }
    get occupation() {
        return `Occupation: ${this._occupation}`
    }
    set occupation(occupation) {
        return this._occupation = occupation;
    }
}
var jane = new Person('Jane');
console.log(jane.fullName); // Doe, Jane
                    

Object.assign

The missing piece
var person = {
    firstName: 'Peter',
    lastName: 'Parker'
};

var superhero = {
    alias: 'Spider-man',
    superpowers: [ 'web', 'wall-climbing' ]
};

var spiderman = Object.assign({}, person, superhero);
console.log(JSON.stringify(spiderman));
/*
{
    "firstName": "Peter",
    "lastName": "Parker",
    "alias": "Spider-man",
    "superpowers": [ "web", "wall-climbing" ]
}
*/

                    

modules

ES5 modules

404

Sorry, the feature you're looking for is not available.

Workarounds:

  • JavaScript does not have built-in support for modules
  • community has created impressive work-arounds
  • two most popular: CJS and AMD
  • unfortunately incompatible

CommonJS

  • compact syntax
  • designed for synchronous loading
  • main use: server
exports as object
// lib/math.js
var MINI_PI = 3.14;
var earthRadius = 6378;
var toFixed = function ( num ) {
    return Math.ceil(num * 100) / 100
};
var circumference = function ( radius ) {
    return toFixed( MINI_PI * ( 2 * radius )  )
};
exports = {
    circumference: circumference,
    MINI_PI: MINI_PI,
    toFixed: toFixed
};
                            
exports individual attributes
// lib/math.js
var MINI_PI = 3.14;
var toFixed = function ( num ) {
    return Math.ceil(num * 100) / 100
};
exports.circumference = function ( radius ) {
    return toFixed( MINI_PI * ( 2 * radius )  )
};
                            
require
var math = require('lib/math');
var earthsCircum = math.circumeference( 6378 );
                                
CommonJS Modules (CJS) The dominant incarnation of this standard is Node.js modules (Node.js modules have a few features that go beyond CJS).

Asynchronous Module Definition (AMD)

  • Slightly more complicated syntax
  • Designed for asynchronous loading
  • main use: browsers
  • The most popular implementation of this standard is RequireJS.
define
// app/geo
define([ 'lib/math' ], function ( math ) {
    var getCircumference = function ( radius ) {
        return math.circumference( radius );
    };

    // export circumference for other modules
    return {
        getCircumference: getCircumference
    };
});
                            
require
// main.js
require([ 'underscore', 'app/geo' ], function ( _, geo ) {
        // main module loads other other modules
        var earthsCircum = geo.getCircumference( 6378 );
        // do other stuff
});
                            
Slightly more complicated syntax enabling AMD to work without eval() or a static compilation step Designed for asynchronous loading main use: browsers The most popular implementation of this standard is RequireJS.

ES6 Modules

The goal for ECMAScript 6 (ES6) modules was to create a format that both users of CommonJS and of AMD are happy with.

Implicitly async model – no code executes until requested modules are available and processed.

import $ from "jquery";                    // import the default export of a module
module crypto from "crypto";               // binding an external module to a variable
import { encrypt, decrypt } from "crypto"; // binding a module's exports to variables
import { encrypt as enc } from "crypto";   // binding and renaming one of a module's exports
export * from "crypto";                    // re-exporting another module's exports
export { foo, bar } from "crypto";         // re-exporting specified exports from another module
                    
A module is simply a file with JavaScript code in it.

exporting module

// lib/math.js
const MINI_PI = 3.14;
const EARTH_RADIUS = 6378;

// this function is private to the module
function toFixed(num) {
    return Math.ceil(num * 100) / 100;
}

// export function
export function circumference(radius) {
    return toFixed(MINI_PI * (2 * radius))
}

// export variables
export var planet = 'Earth';
export let distanceFromSun = 149600000;
export MINI_PI;

// export class
export class Rectangle {
    constructor(length, width) {
        this.length = length;
        this.width = width;
    }
}

---

// importing

import { circumference } from './circumference';
import { EARTH_RADIUS } from './constants';
let earthCircum = circumference(EARTH_RADIUS);

circumference = 40075; // error

console.log(`Earth's circumference: ${ earthCircum }km.`); // "Earth's circumference: 40074.16km."

---

// math.js
function sum(num1, num2) {
    return num1 + num2;
}

export { sum as add };

// main.js
import { add } from './math';

// or rename during import

// math.js
function sum(num1, num2) {
    return num1 + num2;
}

export sum;

// main.js
import { add as sum } from './math';
console.log(typeof add); // 'undefined'
console.log(sum(1, 2)); // 3

---

// math.js
export default function sum(num1, num2) {
    return num1 + num2;
}

// main.js
import sum from './math';
console.log(sum(1, 2)); // 3

---

// math.js
export MINI_PI = 3.14;
export default function sum(num1, num2) {
    return num1 + num2;
}

// main.js
import sum, { MINI_PI } from './math';
console.log(sum(1, 2)); // 3
console.log(MINI_PI); // 3.14

---

import { sum } from './math';
export { sum }

export { sum } from './math';
export { sum as add } from './math';
export * from './math';


                    

exporting variables and functions

// lib/math.js
const MINI_PI = 3.14;
let earthRadius = 6378;
let toFixed = num => Math.ceil(num * 100) / 100
export MINI_PI;
export function circumference ( radius ) {
    return toFixed( MINI_PI * ( 2 * radius )  )
}
                        

importing

Import declarations bind another module’s exports as local variables. Imported variables may be locally renamed to avoid conflicts.
import as a module
// app.js
module math from 'lib/math';
let earthCircum = math.circumference( 6378 );
log(`earth's circumference: ${earthCircum}`);
                            
import as variables
// app.js
module { MINI_PI, circumference } from 'lib/math';
let earthCircum = circumference( 6378 );
log(`earth's circumference: ${earthCircum}`);
                            
renaming
module { MINI_PI as PIE, circumference as circum } from 'lib/math';
                            

default export

default export
module "foo" {
    export default function () {
        console.log("hello!");
    }
}
                            
import
import foo from "foo"; // no need for curly braces
foo(); // hello!
                            

New standard library features:

  • Set
  • Map
http://www.nczonline.net/blog/2014/04/22/creating-defensive-objects-with-es6-proxies/

Set

A set is in an ordered list of values that cannot contain duplicates.
var fruits = new Set();
fruits.add('banana');
fruits.has('banana'); // true
fruits.add('apple');
fruits.size; // 2
                    
Set methods
console.log( Object.getOwnPropertyNames(Set.prototype) );
// [ "size", "has", "add", "delete", "clear" ]
                        
Sets are nothing new if you come from languages such as Java, Ruby, or Python but have been missing from JavaScript. You typically don't access items in the set like you would items in an array, instead it's much more common to check the set to see if a value is present.

Set and coercion

var items = new Set();
items.add(5);
items.add("5");
console.log(items.size()); // 2
                    
ECMAScript 6 sets do not coerce values in determining whether or not to values are the same. So, a set can contain both the number 5 and the string "5" (internally, the comparison is done using Object.is()). If the add() method is called more than once with the same value, all calls after the first one are effectively ignored.

Converting Array to Sets

You can initialize the set using an array, and the Set constructor will ensure that only unique values are used.
var items = new Set([1, 2, 2, 3, 3, 4, 5, 5, 5, 5]);
console.log(items.size()); // 5
                    

Map

Map type is an ordered list of key-value pairs where both the key and the value can be of any type.

Why do we need Map?

Quiz
var library = {};
var LOTR = { title: 'The Lord of The Rings', author: 'J.R.R Tolkien' };
var TLP = { title: 'The Little Prince', author: 'Antoine de Saint-Exupery' };
library[LOTR] = 'The Lord of The Rings';
library[TLP] = 'The Little Prince';
console.log( library[LOTR] ); // ???
console.log( library[TLP] ); // ???
                    

Map - proper way

var library = new Map();
var LOTR = { title: 'The Lord of The Rings', author: 'J.R.R Tolkien' };
var TLP = { title: 'The Little Prince', author: 'Antoine de Saint-Exupery' };
library.set(LOTR, 'The Lord of The Rings');
library.set(TLP, 'The Little Prince');
library.get(LOTR); // "The Lord of The Rings"
library.get(TLP) ); // "The Little Prince"
// DOM Nodes
var header = document.getElementById('header');
library.set(header, 'some metadata');
                    
ECMAScript 6 sets do not coerce values in determining whether or not to values are the same. So, a set can contain both the number 5 and the string "5" (internally, the comparison is done using Object.is()). If the add() method is called more than once with the same value, all calls after the first one are effectively ignored:

Map methods

console.log( Object.getOwnPropertyNames(Map.prototype) );
// [ "size", "get", "has", "set", "delete" ]
                    

part four

New standard library features:

  • WeakMap + WeakSet
  • Promises
  • Proxies
http://www.nczonline.net/blog/2014/04/22/creating-defensive-objects-with-es6-proxies/

...rest of goodies

  • symbols
  • template strings
  • unicode
  • module loaders
  • subclassable built-ins
  • math + number + string + object APIs
  • binary and octal literals
  • reflect api
  • tail calls

Questions?

Thank you