What's the future of JavaScript?

By Francisco Ramos

Who am I?

Backgrounds

  • Desktop apps using Visual Basic
  • Backend dev using PHP (sometimes Python)
  • I taught a little bit of Java

During my backend times I was always doing frontend: HTML + CSS + JS

I was a real Web Developer

... and I became Frontend

  • It took me 3 years to really understand the beauty and power of JavaScript
  • When that happened a light bulb went off in my head
  • I became exclusively Frontend working as a UI developer

Since then, I've been doing only HTML + CSS + JavaScript

... but my passion is architectures, libraries, frameworks, code conventions, best practices...

I'm sort of a

JavaScript evangelist

What's so cool about JavaScript?

  1. JavaScript is really and purely an object oriented language:
    • Everything is an object, even literals.
    • It's a prototype-based language.
    • No inheritance, but delegation.
  2. First-class function:
    • Passing functions as arguments.
    • Returning them as values.
    • Assigning them to variables or data structures.
  3. Closures: the possibility for a function to remember its surroundings

A little bit of History

  • 1995: JavaScript was born (LiveScript). Later on was standarized and ECMAScript was created
  • 1997: ES1
  • 1998: ES2
  • 1999: ES3. RegExp, better string handling and Try Catch block
  • 2000: ES4 started, but was abandoned in 2004
  • 2011: ES5. JSON, Function.prototype.bind, lots of new methods in Array.prototype, Object.create, Object.defineProperty.
  • 2012: ES6 (Harmony). Proposal freeze in 2013. Release 2014-2015

ES6 Harmony

new features and sugar

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

modules

Feature for scalable applications

// Profile.js

export var firstName = 'Francisco';
export var lastName = 'Ramos';
export var year = 1978;
// ProfileView.js

import {firstName, lastName} from './Profile';
import {year as yearOfBirth} from './Profile';

function setHeader(element) {
  element.textContent = firstName + ' ' + lastName;
}

Exports and Imports

// lib/math.js

let PI = 3.14159; // not exported

export function sum(a, b) { return a+b; }
export function square(x) { return x*x; }
export function area(r) { return PI*(r^2); }
// main.js

module math from 'lib/math';

console.log(math.square(3));
// main2.js

import * as math from 'lib/math';

console.log(math.area(5));

Importing the whole module

// main3.js

import 'lib/math' as math;

console.log(math.sume(2, 5));
// lib/utils.js

export const PI = 3.14159;

export default {
    func1() {},
    func2() {},
    func3() {}
};
// app.js

import {PI} from 'lib/utils';
import utils from 'lib/utils';

console.log(PI);
utils.func1();

Default exports

Re-exporting things

// something.js

export * from 'Profile';
export {sum, square} from 'lib/math';
// main.js

import 'something' as stuff;

console.log(stuff.firstName);
console.log(stuff.lastName);
console.log(stuff.year);
stuff.sum(2, 2);
stuff.square(4);

... and more

  • Inline modules
  • Module script tag
  • Programmatic loader API
  • Circular references detection
  • Async, non blocking loading

Proxies

Extending JavaScript

Meta-programming in JavaScript

let Person = {
    name: 'Fran',
    age: 35
};

let PersonWrap = Proxy(Person, Interceptor);
let Interceptor = {
    get(target, prop, receiver) {}, // personWrap.name
    set(target, prop, val, receiver) {}, // personWrap.age = 36
    delete(target, prop) {}, // delete personWrap.name
    has(target, prop) {}, // 'name' in personWrap
    enumerate (target) {} // for (let prop in personWrap) {}
};

... and much more

{
  getOwnPropertyDescriptor: function(target,name) {},         // -> Object.getOwnPropertyDescriptor(proxy,name)
  getOwnPropertyNames:      function(target) {},              // -> Object.getOwnPropertyNames(proxy) 
  getPrototypeOf:           function(target) {},              // -> Object.getPrototypeOf(proxy)
  defineProperty:           function(target,name, desc) {},   // -> Object.defineProperty(proxy,name,desc)
  deleteProperty:           function(target,name) {},         // -> delete proxy[name]
  freeze:                   function(target) {},              // -> Object.freeze(proxy)
  seal:                     function(target) {},              // -> Object.seal(proxy)
  preventExtensions:        function(target) {},              // -> Object.preventExtensions(proxy)
  isFrozen:                 function(target) {},              // -> Object.isFrozen(proxy)
  isSealed:                 function(target) {},              // -> Object.isSealed(proxy)
  isExtensible:             function(target) {},              // -> Object.isExtensible(proxy)
  hasOwn:                   function(target,name) {},         // -> ({}).hasOwnProperty.call(proxy,name)
  keys:                     function(target) {},              // -> Object.keys(proxy)
  apply:                    function(target,thisArg,args) {}, // -> proxy(...args)
  construct:                function(target,args) {}          // -> new proxy(...args)
}

Must watch: Proxies are awesome by Brendan Eich

Promises

No more callbacks

Pyramid of doom :-(

(function () {

    function init() {
        fetchHtml('http://html.json', function (data) {
            buildDOM(data, function () { // async??
                fetchContent('http://content.json', function (data) {
                    bindContent(data, function () {
                        alert("I'm done!!");
                    })
                });
            });
        });
    }

    init();

})();

Refactored version :-\

// hoisting callbacks

(function () {

    function onContentBound() { alert("I'm done!!"); }
    function onContentFetched(data) { bindContent(data, onContentBound); }
    function onDOMBuilt() { fetchContent('http://content.json', onContentFetched); }
    function onHtmlFetched(data) { buildDOM(data, onDOMBuilt); }
    function init() { fetchHtml('http://html.json', onHtmlFetched); }

    init();

})();

Using promises :-)

(function () {

    init()
        .then(fetchHtml)
        .then(buildDOM)
        .then(fetchContent)
        .then(bindContent)
        .then(() => alert("I'm done!!") )
        .catch(() => alert("Something when wrong!!") )
    ;

})();

... and if the promise is resolved before registering the callback, once we do it, it's called immediately.

API

/* Constructor */
new Promise(function (resolve, reject) {});

/* Methods */
Promise.prototype.then(onFulfilled, onRejected)
Promise.prototype.catch(onRejected) == Promise.prototype.then(null, onRejected)

/* Static methods */
Promise.resolve(value) // -> resolved Promise
Promise.reject(reason) // -> rejected Promise
Promise.all(...promises) // -> Promise that will resolve when all promises resolved

=> functions

It's all about this

Arrows share the same lexical this as their surrounding code

var Form = {

  init: function () {
    this.$input = document.querySelector('input');
    this.$button = document.querySelector('button');
    this.attachEvents();
  },

  attachEvents: function () {

    // ES3 - var that = this
    var that = this; // scope variable for closure
    document.addEventListener('click', this.$button, function (e) { that.onButtonClick(e) });

    // ES5 - Function.prototype.bind
    document.addEventListener('click', this.$button, this.onButtonClick.bind(this) );

    // ES6 - arrow function
    document.addEventListener('click', this.$button, e => this.onButtonClick(e) );

  }

  onButtonClick: function (e) { /* we do something when the use clicks on the button */ }

};

let: block scope

The new var

The problem

// 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 = []; 
var count; // this variable is hoisted

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

Solution (a nasty one)

// ES3, ES5
var handlers = [];

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

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

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

... and much more

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; }
}

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

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

Map

The basic idea is to map a value to a unique key, 

any type of key, even objects

let myMap = new Map();
let key1 = 'bla';
let key2 = document.getElementById("myDiv");

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

// later
let value = myMap.get('bla');
let meta = myMap.get(document.getElementById("myDiv"));

WeakMap

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

let wMap = new WeakMap();
let 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 :-)

generators: yield

co-routines/multi-task

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

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

More new syntax

  • Iterate over infinite streams
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...
}
var fib = fibonacci();

console.log(fib.next()); // 1
console.log(fib.next()); // 2
console.log(fib.next()); // 3
console.log(fib.next()); // 5
console.log(fib.next()); // 8
  • We can create custom iterators
function* keyValue(obj) {
    for (let k in obj) {
        yield [k, obj[k]];
    }
}

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

for (let [key, val] of keyValue(Person)) { ... }
  • Asynchronous programming
let userTasks = (function* () {

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

userTasks.next() // gets the user
  .then( user => {
    // we do something with the user and then...
    return userTasks.next(user); // processes the user
  }) 
  .then( user => {
    // we do something else after the user has been processed and then...
    return userTasks.next(user); // saves the user
  })
  .then( user => {
    // final operations here
  })
;

Elegant way of array processing

array comprehensions

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

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

//2. Array map
let pow2 = [for (x of arr) Math.pow(x, 2)];
console.log(pow2); // [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; });
console.log(mult2); // [2, 4]

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

Picked up from languages such as Python or CoffeeScript

destructuring

Saving you lots of typing

Destructuring arrays

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

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

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

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

Destructuring of function arguments

// ES3, ES5
function ajax (config) {
    var url = config.url;
    var data = config.data || {};
    var method = config.method || 'get';
    var 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, data, method, complete }) {
    console.log(url, data, method, complete);
}

enhanced object literals

Even more typing saving

// ES3, ES5

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

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

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

Too much typing. We need something simpler...

// ES6

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

var Person = {
    name: 'Fran',
    getTypeId, // shorthand for getTypeId: getTypeId
    sayName() { alert('My name is ' + this.name); },
    [getTypeId()] = '12341234x' // computed property names
};

Object-based design

var vehicle = {
    color: '',
    speed: 0,
    accelerate(up) { this.speed += up; },
    slowDown(down) { this.speed -= down; }
};

var Ferrari = {
    __proto__: vehicle,
    color: 'red',
    accelerate(up) { super.accelerate(up + 30); }
};

var plane = {
    __proto__: vehicle,
    color: 'white',
    altitude: 0,
    takeOff(up) {
        super.accelerate(300);
        this.altitude += up;
    },
    fly() { this.takeOff(10000); }
};

...rest and ...spread operators

Fixing "misdesign"

// ES3, ES5
    
function foo (x, y) {
    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) {

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

}

It gets weirder though

// ES3, ES5

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

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

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

Wait, it gets better...

function storeUserDetails(name, age, country) {
    $.ajax('http://www.example.com/storeUser', {...});
}

let userInfo = ['Fran', 36, 'Spain'];

// ES5
storeUserDetails.apply(null, userInfo);

// ES6
storeUserDetails(...userInfo);

for-of loops

Complementing for-in

TO-DO

default parameters

Function params values by default

TO-DO

classes

Let's make everybody happy

This is what a class will look like

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

Let's have a look at the inheritance

class MySubType extends MyType { // MySubType.prototype = Object.create(MyType.prototype);

    constructor(arg1, arg2) {
        super(arg1); // MyType.call(this, arg1)
    }
    
    pubMethod (arg1) {
        super.pubMethod(arg1); // MyType.prototype.pubMethod.call(this, arg1)
    }

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

}

... and much more coming up

  • Binary data
  • String interpolation
  • Standard modules
  • Deferred functions
  • Symbols
  • Unicode support
  • Object.observe (ES7)
  • Number additions
  • String additions
  • Math additions
  • RegExp additions
  • etc...

play aroung

learn more

that's all folks!

What's the future of JavaScript

By Francisco Ramos

What's the future of JavaScript

  • 1,362