ecmascript 6

The future of JavaScript
By
Francisco Ramos

New features and sugar

  1. let: block scope
  2. for-of loops
  3. Default arguments
  4. ...rest and ...spread operators
  5. Array comprehensions
  6. Destructuring
  7. Shorthands
  8. => function or lambda
  9. Generators: yield
  10. Privacy with Symbol object
  11. Set, Map and WeakMap objects
  12. Proxies
  13. Classes
  14. Modules

let: block scope

The new var
// ES3, ES5

var handlers = [];

for (var count = 0; count < 3; count++) {
    handlers[count] = function () {
        alert(count);
    }
}

handlers[0](); // alerts "3"
handlers[1](); // alerts "3"
handlers[2](); // alerts "3"
        
// ES3, ES5
var handlers = [], 
    count; // this variable is hoisted

for (count = 0; count < 3; count++) {
    handlers[count] = function () {
        alert(count);
    }
}
        
handlers[0](); // alerts "3"
// Solution in ES3, ES5
var handlers = [];

for (var count = 0; count < 3; count++) {
    (function (i) {
        handers[i] = function () {
            alert(i)
        };
    })(count);
}

handlers[0](); // alerts "0"
handlers[1](); // alerts "1"
handlers[2](); // alerts "2"
// ES6

var handlers = [];

for (let count = 0; count < 3; count++) {
    handlers[count] = function () {
        alert(count);
    }
}
        
handlers[0](); // alerts "0"

let x = 1; // let-definition

let(y = 2, z = 3) { // let-statement
    // block where "x", "y" and "z" are visible
}

// scope where only "x" is visible

{ // new block scope
    let i = 0; // will be visible within this block
}

let (
    privVal = 5,
    myPrivate = function () { /* do something private */ }
) {
    var something = myPrivate();
    function getVal() { return privVal; }
}

for-of loops

Complementing for-in
// ES3, ES5
for (var x in [3, 4, 5]) {
    alert(x); // 0, 1, 2 :-\
}
// ES6
for (let y of [3, 4, 5]) {
    alert(y); // 3, 4, 5 :-D
}

for (let z of obj) {
    alert(z); // values of the object
}


let k, v;
for (k of keys(obj)) {} // keys of the object
for (k of values(obj)) {} // values of the objects
for ([k, v] of items(o)) {} // pair key-value


// keys ?? => Object.keys or import keys from "@iter"
// values ?? => Object.values or import values from "@iter"
// items ?? => Object.items or import items from "@iter"
// ES6
import iterate from "@iter";

obj[iterate] = function () {
    return {
        next: function () {
            // custom iteration
            return nextValue;
        }
    }
};

for (let v of obj) { ... }
// latest proposal in harmony:iterators
obj.iterator = function () {
    return {
        next: function () { ... }
    }
}

for (let p of obj) { ... }

Default arguments

Function params values by default
// ES3, ES5
function ajax (url, data, method) {
    if (typeof data === 'undefined') data = {appName: 'campaign'};
    if (typeof method === 'undefined') method = 'get';

    // data = data || {...};
    // method = method || 'get';
    
    // ... and your code begins in here...

}
// ES6
function ajax (url, data = {appName: 'campaign'}, method = 'get') {

    // less confusing and cleaner code in here ;-)
    
}

...rest and ...spread operators

Fixing "mis-design"
// ES3, ES5
    
function foo (x, y /*, rest args */) {
    var rest = [].slice.call(arguments, 2);
    
    // (arguments instanceof Array) === false
    
    // now "rest" is a real array
    rest.slice( ... );
    rest.map( ... );
}
        
// ES6

function foo (x, y, ...rest /* or ...whatever */) {

    // "rest" is a real array!!
    
    for (let i of rest) { ... }
    rest.slice( ... );
    rest.filter( ... );
    rest.map( ... );

}

It gets weirder though...

// ES3, ES5

var ARR_SLICE = Array.prototype.slice;

function MyType() {
    var args = ARR_SLICE.call(arguments);
    this.processArgs.apply(this, args); // I mean, WTF!? O_O
}

MyType.prototype.processArgs = function (arg1, arg2) {
    var rest = ARR_SLICE.slice.call(arguments, 2);
};
// ES6
    
var MyType = function (...args) {
    this.processArgs(...args);
}

MyType.prototype.processArgs = function (arg1, arg2, ...rest) {
    // "rest" is an array
}

wait, now it gets better ;-)

// ES6

function storeUserDetails(name, age, country) {
    $.ajax('http://www.whatever.com/webservice', {
        data: { ... }
        success: function () { ... }
    });
}

//...

var userInfo = ['Fran', 34, 'Spain'];

// ES3-5 way: storeUserDetails.apply(null, userInfo);
storeUserDetails(...userInfo); // makes more sense :-)

Out of curiosity :-P

// ES6

let points = [1, 2, 3, 4, ...restPoints];

let shape = new Shape(...points); //there is no way in ES3 to do this

// There is a crazy way to do it in ES5
var shape = Shape.apply(Shape.bind(Shape.prototype), points);

Array comprehensions

Elegant way of array processing
// ES3
var arr = [1, 2, 3, 4, 5];

//1. Array filter
//too complicated to put vanilla js in here. Using jQuery:
var mult2 = $.grep(arr, function (val, i) { 
    return val % 2 == 0;
});
console.log(mult2); // will log [2, 4];

//2. Array map
var pow2 = $.map(arr, function (val, i) { 
    return Math.pow(val, 2); 
});
console.log(pow2); // will log [1, 4, 9, 16, 25];
// ES5
var arr = [1, 2, 3, 4, 5];

//1. Array filter
var mult2 = arr.filter(function (val, i) { return val % 2 == 0; });

//2. Array map
var pow2 = arr.map(function (val, i) { return Math.pow(val, 2); });

Picked up from languages such as Python or CoffeeScript:

// ES6

let arr = [1, 2, 3, 4, 5];

//1. Array filter
let mult2 = [x for (x of arr) if (val % 2 == 0)];

//2. Array map
let pow2 = [Math.pow(x, 2) for x of arr];

let name = 'Franito';

//3. More complex operations
let onlyUCVowels =
[c.toUpperCase() for(c of name) if (c.match(/[aeiou]/))].join('');
    
console.log(onlyUCVowels); // will log "AIO";

Destructuring

Saving you lots of typing

Destructuring arrays

//ES6

let [x, y] = [10, -10, 5];
console.log(x, y); // 10, -10;

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

// variables swapping
[a, b] = [b, a];

Destructuring objects

// ES6

let user = {name: 'Fran', age: 34, location: [15, -20]};

let {name: n, age: a, location: l} = user;
console.log(n, a, l[0], l[1]); // Fran, 34, 15, -20

// or
let {name, age, location: [lat, lng]} = user;
console.log(name, age, lat, lng); // Fran, 34, 15, -20

Destructuring of function arguments

// ES3, ES5
function ajax (config) {
    var url = config.url,
        data = config.data || {},
        method = config.method || 'get',
        complete = config.complete || new Function;
        
    // this is too "noisy" :-\
}

ajax({
    url: 'http://www.whatever.com/method',
    data: { ... },
    method: 'post',
    complete: function () { ... }
});
// ES6

function ajax ({
    url: u, 
    data: d, 
    method: m, 
    complete: c
}) {
    //now we have "u", "d", "m", "c" variables within this function
}

// or
function ajax ({ url, data, method, complete }) {
    console.log(url, data, method, complete);
}

Shorthands

Even more typing saving
// ES3, ES5

function getTypeId() { return 'passport'; };

var myObj = {
    name: 'Fran',
    getTypeId: getTypeId,
    sayName: function () {
        alert('My name is ' + this.name);
    }
};

myObj[getTypeId()] = '12341234x';

//TOO MUCH typing, we need something better
// ES6

function getTypeId() { return 'passport'; };

var myObj = {
    name: 'Fran',
    getTypeId,
    sayName() {
        alert('My name is ' + this.name);
    },
    [getTypeId()] = '12341234x'
};

//MUCH better :-)

=> function or lambda

It's all about this
// ES3, ES5
var MyController = function ($http) {
    this.httpService = $http;
};

MyController.prototype = {
    constructor: MyController,
    ajax: function (settings) {
        this.httpService(settings)
            .success(this.onSuccess) // There is a problem in here
            .error(this.onError) // ...and in here
        ;
    },
    onSuccess: function () { ... },
    onError: function () { ... }
};

//...        
onSuccess: function (response) {
    if (response.status == 'OK') {
        var data = this.sanitize(response.data); // BAM!! error
    }
},
//...
    
// Solution in ES3
//...
ajax: function (settings) {
    var self = this; // scope variable for closure  :-/
    
    this.httpService(settings)
    .success(function (resp) { self.onSuccess(resp); })
    .error(function (resp) { self.onError(resp); });
}
//...
// Solution in ES5
//...
ajax: function (settings) {
    this.httpService(settings)
    .success((function (resp) { this.onSuccess(resp); }).bind(this))
    .error((function (resp) { this.onError(resp); }).bind(this));
}
//...

Copied from CoffeeScript and C#??

// ES6
//...
ajax (settings) {
    this.httpService(settings)
        .success(resp => this.onSuccess(resp))
        .error(resp => this.onSuccess(resp))
    ;
}
//...

setTimeout(() => {
    var now = Date.now();
    this.renderTime(now);
}, 1000);

document.addEventListener('click', e => this.onClick(e), false);

// or simply to type less ;-)
(name, age) => alert("My name is ${name} and I'm ${age} years old");
//WOW!!, string interpolation! :-)

Generators: yield

co-routines/multi-task

New feature in Javascript

// ES6

function* myGenerator() {
    // some code here
    yield myTask1();
    yield myTask2();
    yield myTask3();
    // more code here
}

let g = myGenerator();
g.next(); // executes myTask1 returning result
g.next(); // executes myTask2 returning result
g.next(); // executes myTask3 returning result

... and what can I do with this ???

Iterate over infinite streams

// ES6
var fibonacci = function* () {
    let [prev, curr] = [0, 1];
    for (;;) { // infinite iteration
        [prev, curr] = [curr, prev + curr];
        yield curr;
    }
};

for (let val of fibonacci()) {
    if (val > 1000) break;
    
    console.log(val); // 1, 2, 3, 5, 8...
}

We can create custom iterators

// ES6
function* keyValue(obj) {
    for (let k in obj) {
        yield [k, obj[k]];
    }
}

let Person = {
    name: 'Fran',
    age: 34,
    country: 'Spain'
}

for (let [key, val] of keyValue(obj)) { ... }

Asynchronous programming

// ES3, ES5

// Callbacks
$.ajax('http://get/user', function (data) {
    $.ajax('http://process/user', function (result) {
        $.ajax('http://save/user', function (response) {
            // Pyramid of doom :-O
        });
    });
});
// Possible solution in ES3, ES5: hoisting functions out

function getUser() { 
    $.ajax('http://get/user', processUser); 
}

function processUser(result) { 
    $.ajax('http://process/user', saveUser); 
}

function saveUser(response) { 
    // a bit clearer but still confusing;
}
// ES6
    
let userTasks = (function* () {

    // Coroutines that return promises
    yield $.ajax('http://get/user');
    yield $.ajax('http://process/user');
    yield $.ajax('http://save/user');
    
})();

let promise;

promise = userTasks.next(); // gets the user
promise = userTasks.next(); // process the user
promise = userTasks.next(); // saves the user

Privacy with Symbol object

Indexing by objects

Privacy in ES3

//1. Module pattern
var MyModule = (function () {
    
    //Private scope
    var privAttr;
    function privFunc() { ... }
    
    return { /* Returns public API */ };
    
})();
// Another variant
var MyModule;
(function (exports) {
    
    var privAttr,
        privFunc = function () { ... };
        
    exports.pubAttr = privFunc();
    exports.pubFunc = function () {
        returns privAttr;
    };
    
})(MyModule || (MyModule = {}));
//2. Constructor pattern. Priviledged methods
var Person = function (name, age) {
    
    //Private members
    var privAttr,
        privMethod = function () { ... };
    
    //Public attributes
    this.name = name;
    this.age = age;

    //Priviledged methods, must be defined in the constructor
    this.getPrivAttr = function () { return privAttr; };
    this.doSomething = function () { privMethod(); };

};

Problem: for every new instance, new memory allocation for each of the private and priviledged methods

// Personal improvement
(function () { // private scope

    var privAttr = 'secret value', // NOT a good solution
        privKey = '__priv_attr__'; // or some difficult string
        
    function privMethod(age) { ... }
    
    function Person (name, age) {
        this[privKey] = 'private'; //not quite private, is it?
    }
    
    Person.prototype.pubMethod = function (age) {
        this.pubAttr = privMethod.call(this, age);
    }
})();

... but not the best since we still need priviledged methods to access private attributes

Privacy in ES5

... hasn't improved much. Although we can now make an attribute be readonly using descriptors
// ES5

var myObj = {};
Object.defineProperty(myObj, 'attrName', {
    value: 'attrValue', // initial value
    get: function () { return value; }, // it's called when reading
    set: function (val) { ... }, // it's called when writing
    writable: false, // can it be overriden?
    enumerable: true, // can it be looped over?
    configurable: false // can we change its configuration?
});

//... and also some non-standard
myObj.__defineGetter__('prop', function () { return prop; });
myObj.__defineSetter__('prop', function (v) { ... })

Privacy in ES6, using private Symbols

// ES6
(function () {

    let secretKeyAttr = new Symbol(), // unique value, NOT a string!!
        secretKeyMethod = new Symbol(); // unique object value
    
    function MyType() {
        this[secretKeyAttr] = this[secretKeyMethod]();
    }
    
    MyType.prototype[secretKeyMethod] = function () {
        return secret_value;
    }

})();

var myType = new MyType();
// mytype[???], no-one knows the keys
// console.log(myType) only shows string keys

Gets a bit better using private@ sugar

// ES6
(function () {

    private @secretAttr;
    private @secretMethod;
    
    function MyType() {
        this.@secretAttr = this.@secretMethod();
    }
    
    MyType.prototype.@secretMethod = function () {
        return secret_value;
    }

})();

let myType = new MyType;

console.log(myType); // Empty object {}

set, map and weakmap objects

Handy tools to make our life easier

set

Ordered list of values that cannot contain duplicates.
Nothing new if you come from Java or Phyton

// ES6
let items = new Set();
items.add(5);
items.add("5");
items.add(5); // oops, duplicate - this is ignored
console.log(items.size); // 2

let items = new Set([1, 2, 3, 4, 5, 5, 5, 'Fran']);
console.log(items.size); // 6
consooe.log(items.has(2)); // true
items.delete('Fran');
consooe.log(items.size); // 5

let elements = new Set(document.querySelector(".element"));
for (let e of elements) {
    // loops over all the elements
}

map

The basic idea is to map a value to a unique key,
any type of key, even objects.

// ES3, ES5
var myMap = {}, 
    key1 = 'bla', 
    key2 = document.getElementById("myDiv");

myMap[key1] = 'value1';
myMap[key2] = 'value2'; // key2 == "[Object HTMLDivElement]"
// ES6
let myMap = new Map(), 
    key1 = 'bla', 
    key2 = document.getElementById("myDiv");

myMap.set(key1, 'value1');
myMap.set(key2, metadata);

// later
let value = myMap.get('bla'),
    meta = myMap.get(document.getElementById("myDiv"));
// ES6. Map shares methods with Set
let myMap = new Map([[key1, val1], [key2, val2]]);
console.log(myMap.has(key1)); // true
console.log(myMmap.get(key1)); // val1
console.log(myMap.size); // 2
myMap.delete(key2);
console.log(myMap.size); // 1

for (let key of myMap.keys()) { 
    console.log("Key: ${key}"); 
}

for (let value of myMap.values()) { 
    console.log("Value: ${value}");
}

for (let item of myMap.items()) {
    console.log("Key: ${item[0]}, Value: ${item[1]}");
}

weakmap

Same as Map objects but the key must be an object.
There is a reason for that...

// ES6
let wMap = new WeakMap(),
    element = document.querySelector(".element");

wMap.set(element, "My Value");
console.log(wMap.get(element)); // "My Value"

// later - remove reference
element.parentNode.removeChild(element);
element = null; // the element gets garbage collected

console.log(wMap.size); // 0

A WeakMap holds only a weak reference to a key

... more efficient memory consumption :-)

proxies

Extending JavaScript
// ES5, Object meta-programming API
var point3D = { x: 5, y: -10, get z() { return this.x * this.y }  };

console.log(Object.getOwnPropertyDescriptor(point3D, 'x'));
// { value: 5, writable: true, enumerable: true, configurable: true }

console.log(Object.getOwnPropertyDescriptor(point3D, 'z'));
/* { 
    get: function() { return this.x * this.y },
    set: undefined,
    enumerable: true, 
    configurable: true 
} */
// Creating objects in ES5
var point3D = Object.create(Object.prototype, {
    x: { /* descriptor */ },
    y: { /* descriptor */ },
    z: { /* descriptor */ }
});

Object.preventExtensions(point3D);
// point3D.k = 3; can't add new properties

Object.seal(point3D); // delete point3D.x; can't delete properties
Object.freeze(point3D); // point3D.x = 7; can't assign properties

Proxies in ES5?

// ES5
var Point = function (x, y) { // this is the proxy
    var __x__ = x, __y__ = y;
    
    Object.defineProperties(this, {
        x: { 
            get: function() { return __x__; },
            set: function(val) {  __x__ = val; },
        },
        y: { ... }
    });
};

var myPoint = new Point(1, 2);

... and that's it.

Both "Point" and "myPoint" don't reflect structural changes, such as add, delete attributes and change their properties, made to the other.

Proxies in ES6

// ES6
let proxy = Proxy.create(handler, proto);

/*
proxy.foo => handler.get(proxy, foo)
proxy.foo = val => handler.set(proxy, foo, val)
proxy.foo(1, 2, 3) => handler.get(proxy, foo).apply(proxy, [1, 2, 3])
proxy.get => handler.get(proxy, get)
*/

... but not just property access!!:

/*
foo in proxy => handler.has(proxy, foo)
delete proxy.foo => handler.delete(proxy, foo)
for(var prop in proxy) { ... } => handler.enumerate()
Object.defineProperty(proxy, 'foo', pd) => 
    handler.defineProperty('foo', pd)
*/
... AND MUCH MORE!!

... but not quite everything :-(

  1. proxy === obj => no
  2. proxy instanceof someFunction => no
  3. Object.getPrototypeOf(proxy) => no
  4. typeof proxy => no

Function Proxies

// ES6
var funcProxy = Proxy.createFunction(hander, call, construct);

/*
funcProxy(1, 2, 3) => call(1, 2, 3)
new funcProxy(1, 2, 3) => construct(1, 2, 3)
funcProxy.prototype => handler.get(funcProxy, prototype)
*/

The last one is quite interesting,
changing the inheritance behaviour ???


Must watch: Proxies are awesome
By Brendan Eich

classes

Let's make everybody happy

Classes in ES6 is pure syntactic sugar.
It's compiled to prototype-based OO pattern

// ES3, ES5
var MyType = function (arg) { // constructor function
    // private members declared with "var"
    // public attributes attached to "this"
    // privileged methods attached to "this"
}

MyType.prototype = { // prototype pattern
    constructor: MyType, // resetting constructor
    // prototype members - will be copied into the instance
}

// inheritance in ES5
function MySubType() { /* constructor */ }
MySubType.prototype = Object.create(MyType.prototype); // inheritance
MySubType.prototype.constructor = MySubType; // resetting
MySubType.prototype.pubMembersHere = ...

In ES3 we need little bit of juggling

// inheritance in ES3
function inherit(child, parent, override) {

    function F() { this.constructor = child; }
    F.prototype = parent.prototype;
    child.prototype = new F; // where the magic happens
    
    if (override && typeof override === 'object') {
        for (var p in override) {
            if (override.hasOwnProperty(p) ) {
                child.prototype[p] = override[p];
            }
        }
    }
    
}
var MySubType = function () { ... };

inherit(MySubType, MyType, { /* new members */ });

// or
MySubType.prototype.pubMember = function () { ... };

Calling super gets crazier :-/

// ES3, ES5
var MySubType = function () {
    MyType.call(this /*, args*/); // this one is not too bad
};

inherit(MySubType, MyType, {

    pubMember: function () {
        MyType.prototype.pubMember.call(this /*, args*/);
        //... I mean, WTF!!
    }
    
});
// Improvement
function inherit(child, parent) {
    function F() { this.constructor = child }
    child.parent = F.prototype = parent.prototype;
    child.prototype = new F;
}
// calling super constructor: MySubType.parent.constructor.call(this);
// calling super methods: MySubType.parent.method.call(this);

... and finally classes

// ES6
class MyType { // new keyword "class"
    // "strict mode";
    
    private @privAttr; // let @privAttr = new Symbol();
    static someProp = true; // MyType.someProp = true;
    
    constructor(arg1, arg2) { // function MyType() {}
        public pubAttr = arg1; // this.pubAttr = arg1;
        this.@privAttr = arg1 * arg2;
    }
    
    pubMethod() { // MyType.prototype.pubMethod = function () {}
        return [MyType.someProp, this.@privAttr];
    }
}

... obviously it looks much cleaner and shows what you really mean when creating new types, or in this case, classes ;-)

Let's have a look at the inheritance...

// ES6
class MySubType extends MyType { // inherit(MySubType, MyType);

    constructor(arg1, arg2) {
        super(arg1); // MySubType.parent.constructor.call(this, arg1)
    }
    
    pubMethod (arg1) {
        super.pubMethod(arg1);
        // MySubType.parent.pubMethod.call(this, arg1)
    }

    get x() {} // Object.defineProperty(this, 'x', { get: function })
    set y() {} // Object.defineProperty(this, 'y', { set: function })

}

The questions is: does JavaScript need classes? No, but it definitely needs a cleaner way of defining custom types

modules

Feature for scalable applications
// ES3, ES5 - module pattern
var MyModule;

(function (exports) {

    // importing from an external module
    var privVal = Another.Module.value,
        privService = Another.Module.Service;

    // exporting functionality
    exports.pubVal = privService();
    exports.pubService = function () { returns privVal; };

})(MyModule || (MyModule = {}));
console.log(MyModule.pubAttr);

var myService = MyModule.pubService; // importing

myService();

... once again, too much of "noise". A sugar is needed

// ES6
module MyModule { // new keyword "module"
    // "strict mode";

    // importing from an external module. New keyword "import"
    import {privVal, privService} from Another.Module.{value, Service}

    // exporting functionality. New keyword "export"
    export let pubVal = privService();
    export function pubService() { return privVal; }

}
console.log(MyModule.pubAttr);

import myService from MyModule.pubService; // importing

myService();

Sweet, isn't it? :-). Gets a bit better...

More ways of importing


import * from MyModule; // defines "pubVal" and "pubService" vars

import {pubVal, pubService} from MyModule; // destructuring

import {$} from 'https://cdn.jquery.com/jquery.js' // WHAT ?!?! O_O

import {maps, search} from 'https://cdn.google.com/google.js'

... and the referred JavaScript files will actually be loaded by your browser before the code is executed... BAM!!!, non-blocking loading :-)

... and much more coming up


  • Binary data
  • String interpolation
  • Standard modules
  • Deferred Functions
  • Unicode support
  • Object Literals
  • Object.observe
  • Number improvements
  • String improvements
  • Math improvements
  • RegExp improvements
  • etc...
  • Visit [wiki.ecmascript.org]

playing around

ES6 tasting

Made with Slides.com