es-2015-talk



es-2015-talk

0 0


es-2015-talk

Slides for my ES2015 javascript talk

On Github PaquitoSoft / es-2015-talk

Javascript ES2015

(a.k.a. ES6)

The future is now

Index

  • Background
  • Transpilers
  • New variables types
  • Arrow functions
  • Promises
  • Default parameters
  • Template strings
  • Classes
  • Modules
  • Enhanced objects

...Index

  • Array extras
  • Destructuring
  • Symbols
  • for...of
  • Rest parameters and spread
  • Generators
  • Proxies

Background

  • Javascript was born on March 1.996 by Brendan Eich at Netscape.
  • The next major release was ES5 (year 2.000)
  • Version ES2015 (a.k.a ES6) currently being adopted
  • Next version ES2016 (a.k.a. ES7)

Current support status: Kangax ES* table

HTML 5 first release 2014/10/28

Transpilers

  • Compile ES2015 javascript to ES5 so most of the browsers can deal with it.
  • Most popular:

Best results when used with Webpack.

New variables type

let

Allows to define a scoped block variable

const

Allows to declare non updatable valuesBeware of objects and arrays: Object.freeze/Object.seal are your friends here.

Variables let

    
configureEvents() {
    for (let key in this.domEvents) {
        let tokens = key.split('|');
        dom.addEvent(
            this.$el,
            tokens[0], 
            tokens[1] || undefined,
            this[this.domEvents[key]].bind(this)
        );
    }

    if (this.defaultNavigation) {
        dom.addEvent(this.$el, 'click', 'a', this.navTo);
    }
}        
    

Variables const

            
const PODCASTS_DATASOURCE_URL = 'https://itunes.apple.com/us/rss/toppodcasts/limit=100/genre=1310/json';
const PODCAST_CACHE_PREFIX = 'podcast-data_';
const PODCASTS_LIST_CACHE_TTL = 1440; // minutes (one day)

const CONFIG = { defaultTTl: 1440 };
console.log(CONFIG); // --> { defaultTTl: 1440 }
CONFIG.defaultTtl = 10;
console.log(CONFIG); // --> { defaultTTl: 10 }

const CONFIG = Object.freeze({ defaultTtl: 1440 });
console.log(CONFIG); // --> { defaultTTl: 1440 }
CONFIG.defaultTtl = 10;
console.log(CONFIG); // --> { defaultTTl: 1440 }
    
If you use freeze you don't need const, it's useless. Arrays can be freezed so you cannot assign new values. Adding, removing throws error. Object.seal() also prevent new properties to be added to the object.

Arrow functions

  • Create functions with less keyboard strokes
  • Useful for anonymous callbacks (watch out your anonymous)
  • REUSE the context of the parent function

Arrow functions code

    
processRoute(path, state = {}) {
    ...
    if (routeConfig) {
        try {
            routeConfig.handler({
                url: _path,
                params: routeConfig.path.match(_path),
                state
            })
            .then(this.navigate.bind(this))
            .catch((navError) => {
                console.error('RouterEngine::navigate# Error navigating:', 
                    navError);
                this.trigger(RouterEvents.navigationEnd);
                this.trigger(RouterEvents.navigationError, navError);
            });
        } catch (err) {
            ...
    
Parenthesis are optional if function has only one parameter

Promises

  • Mainly designed to avoid callback hell
  • Easier to chain async (and sync) functions
  • Allow for grouping tasks together
  • Allow to create resolved promises
  • Easier to reason about errors in a chain
  • New browser APIs are promise based (ex: fetch, Service Workers)

Promises code - I

    
static findById(podcastId) {
    let cacheKey = PODCAST_CACHE_PREFIX + podcastId,
        podcast = lscache.get(cacheKey);

    if (podcast) {
        return Promise.resolve(podcast);
    } else {
        return new Promise(function(resolve, reject) {
            getPodcastLite(podcastId)
                .then(fetchPodcastFeedUrl)
                .then(fetchPodcastEpisodes)
                .then(function(data) {
                    lscache.set(cacheKey, data, PODCAST_DETAIL_CACHE_TTL);
                    resolve(data);
                })
                .catch(reject);
        });
    }
}
    

Promises Code - II

    
{
    path: '/podcast/:podcastId',
    handler: function podcastDetailHandler(context) {
        return new Promise((resolve, reject) => {
            PodcastModel.findById(context.params.namedParams.podcastId)
                .then(function(data) {
                    resolve({
                        Controller:PodcastPageController, 
                        data: {
                            podcast: data
                        }
                    });
                })
                .catch(reject);
        });
    }
}
    

Default parameters

  • No more existance checking inside body function
  • Evaluated at call time (new objects created on every call)
  • Does not work with null as it is a value. Only apply for undefined values

Default parameters code

    
trigger(eventName, data = {}) {
    let listeners = this.eventsRegistry[eventName] || [];

    for (let listener of listeners) {
        try {
            listener.call(this, data);
        } catch (e) {
            console.warn(`Error in event (${eventName}): ${e.message}`);
        }
    }
}
    

Template strings

  • Allow very easy and mantainable string interpolation
  • Better multiline support (if you need it)
  • Interpolation is not only for values, they are expressions (allow function calls and operations)
  • Tagged templates: custom function to build the output string from a template
  • One of my favorites

Template strings code - I

    
function fetchPodcastFeedUrl(podcast) {
    return new Promise(function(resolve, reject) {
        ajax.getJsonp(`${PODCAST_ID_DATASOURCE_URL}?id=podcast.id`)
            .then(function(data) {
                if (data.results.length) {
                    podcast.feedUrl = data.results[0].feedUrl;
                    resolve(podcast);
                } else {
                    reject(new Error('No feed Url found for podcast: ' +
                        podcast.id));
                }
            })
            .catch(reject);
    });
}

function foo() { return 42; }
console.log(`The meaning of life is: ${foo()}`);
    

Template strings code - II

    
// Code from MDN
var a = 5;
var b = 10;

function tag(strings, ...values) {
  console.log(strings[0]); // "Hello "
  console.log(strings[1]); // " world "
  console.log(values[0]);  // 15
  console.log(values[1]);  // 50

  return "Bazinga!";
}

tag`Hello ${ a + b } world ${ a * b}`;
// "Bazinga!"
    

Classes

  • Javascript has prototypal inheritance
  • Javascript plays very well with module pattern
  • Please, do not begin creating classes just because you can
  • Allows to describe inheritance in a more succint way
  • Main features:
    • easy inherited constructors
    • allow super calls
    • declarative static methods
    • declarative getters and setters

Classes code - I

    
class BaseController {

    constructor(options = {}) {
        this.data = options.data;
        this.template = options.template;
        this.partials = options.partials;
        this.domEvents = options.domEvents;
        this.defaultNavigation = 
            (typeof options.defaultNavigation === 'undefined') ? 
                true : options.defaultNavigation;

        // https://developer.mozilla.org/en-US/docs/Web/API/DOMParser
        // http://caniuse.com/#feat=xml-serializer
        this.domParser = new DOMParser();
    }
...
    

Classes code - II

    
class EpisodeController extends BaseController {

    constructor(data) {
        super({
            data,
            template: EpisodePageTemplate,
            partials: {
                podcastSidebar: PodcastSidebarPartialTemplate
            }
        });
    }

}
    
Don't forget to mention 'static' functions (promises code-1)

Classes code - III

    
class Foo extends Bar {
    constructor() {
        this._degrees = 0;
    }
    sayHi(name) {
        super.sayHi(`Awesome ${name}`);
    }
    get degrees() {
        return this._degrees;
    }
    set (value)
        this._degrees = (value * 2);
    }
}

let bar = new Foo();
console.log(bar.degrees); // -> 0
bar.degrees = 15;
console.log(bar.degrees); // -> 30
    

Modules

  • Standarized way of authoring JS components
  • export components from your module then import them from another one
  • Eases setting private and public code in your components
  • Using import granularity allows for tree shaking
  • Not supported in any browser right now
  • Module loaders not in the spec

Modules code - I

    
// plugins/dom.js
function matches($el, selector) {
    let _matches = $el.matches || $el.msMatchesSelector;
    return _matches.call($el, selector);
}

export function findEl(selector, container) {
    return (container || document).querySelector(selector);
}

// controllers/home-controller.js
import * as dom from '../plugins/dom';

update() {
    let $updatedEl = this.render(),
        $prevPodcasts = dom.findEl('.podcasts', this.$el);

    dom.findEl('.badge', this.$el).innerHTML = this.data.podcasts.length;
    $prevPodcasts.parentNode.replaceChild(
        dom.findEl('.podcasts', $updatedEl),
        $prevPodcasts
    );
}
    

Modules code - II

    
// models/podcast.js
class Podcast {
    ...
}

export default Podcast;

// config/routes.js
import PodcastModel from '../models/podcast';

{
    path: '/',
    handler: function homePageController() {
        return new Promise((resolve, reject) => {
            PodcastModel.findAll()
                .then(function(data) {
                    ...
                })
                .catch(reject);
        });
    }
}
    

Modules code - III

    
// plugins/router.js
export let RouterEvents = Object.freeze({
    navigationStart: 'navigationStart',
    navigationEnd: 'navigationEnd',
    navigationError: 'navigationError',
    routeNotFound: 'routeNotFound'
});

export class RouterEngine extends EventsEmitter {
    static navTo(path, state = {}) {
        window.history.pushState(state, '', `#${path}`);
        window.dispatchEvent(new PopStateEvent('popstate', { state }));
    }
    ...
}

// controllers/base-controller.js
import { RouterEngine } from '../plugins/router';

navTo(event, $target) {
    event.preventDefault();
    RouterEngine.navTo($target.getAttribute('href'));
}
    

Enhanced objects

  • Set prototype at declaration time (__proto__)
  • Shorthand for same name properties
  • Shorter function members syntax
  • Allow computed property names
  • super keyword available

Enhanced objects code

    
constructor(data) {
    super({
        data,
        template: PodcastPageTemplate,
        partials: {
            podcastSidebar: PodcastSidebarPartialTemplate
        },
        defaultNavigation: false,
        domEvents: {
            'click|.podcast-sidebar a': 'navTo',
            'click|.podcast-episodes a': 'navToEpisode'
        }
    });
}

let obj = {
    name: 'Luke',
    sayHi() {
        return `Greetings from ${this.name}`;
    }
};
    

...Index

  • Array extras
  • Destructuring
  • Symbols
  • for...of
  • Rest parameters and spread
  • Generators
  • Proxies

Array extras

Array.from converts array-like objects into truly arrays

    
export function findEls(selector, container) {
    return Array.from((container || document).querySelectorAll(selector));
}

let productsImages = findEls('img.product-detail').map(($product) => {
    return $product.getAttribute('src');
});

// Also (only in Firefox)
for (let el of (container || document).querySelectorAll(selector)) {
    console.log(el);
}
    

Destructuring

  • Pick selected properties from objects and arrays
  • Allow for default values (if none, undefined is the default)
  • Very useful when importing modules to allow tree shaking (ex: Rollup.js)
    
// Router
export let RouterEvents = {}
export class RouterEngine extends EventsEmitter {}

// BaseController
import { RouterEngine } from '../plugins/router';

// Arrays
let arr = ['Rollo', 'Tomassi', 46];
let [name, surname, age] = arr;

// Defaults
var { x = 3 } = {};
console.log(x);
// 3
    

Symbols

  • Produce unique values
  • Is a new primitive type
  • Allow for hidden like class properties

Symbols code

    
var MyClass = (function() {

  // module scoped symbol
  var key = Symbol("key");

  function MyClass(privateData) {
    this[key] = privateData;
  }

  MyClass.prototype = {
    doStuff: function() {
      return this[key];
    }
  };

  return MyClass;
})();

var c = new MyClass("hello")
console.log(c["key"]); // undefined
console.log(c.doStuff()); // "hello"

console.log(c[Object.getOwnPropertySymbols(c)[0]]); // "hello"
    

for...of

  • Allow iteration over new and old types: Map, Set and Array, arguments
  • Iterates over values, not properties
  • Promising iteration over NodeList (only Firefox right now)
    
let arr = [3, 5, 7];
arr.foo = "hello";

for (let i in arr) {
    console.log(i); // logs "3", "5", "7", "foo"
}
for (let i of arr) {
    console.log(i); // logs "3", "5", "7"
}
    

Rest parameters

  • A true array arguments object
  • No more arguments.slice()
    
function sum(base, ...numbers) {
    result = base;
    numbers.forEach(num => {
        result += num:
    });
    return result;
}
let x = 10;
console.log(sum(x, 2, 4, 5, 8)); // 29

// OLD
var args = Array.prototype.slice.call(arguments);
    

Spread operator

  • Use arrays values separately
  • Turn NodeLists into real arrays (FTW)
    
function sum(a, b, c) {
    return a + b + c;
}
let args = [2, 5, 7];
// sum.apply(null, args);
sum(...args);

let spans = document.querySelectorAll('span');
spans.forEach((el) => console.log(el.tagName));
// ---> ERROR
[...spans].forEach((el) => console.log(el.tagName));
// ---> "SPAN", "SPAN",...
    

Generators

  • They were supposed to help async code
  • Complex syntax
  • I'd rather wait for async/await from ES2016

Proxies

  • Object interceptors (objects and functions)
  • Run custom code on object basic operations
  • Allowed traps: getPrototypeOf, setPrototypeOf, isExtensible, preventExtensions, getOwnPropertyDescriptor, defineProperty, has, get, set, deleteProperty, enumerate, ownKeys, apply, construct

Proxies code - I

    
let person = new Observable({
    name: 'Rollo Tomassi',
    age: 47
});

person.onPropertyChanged(function(property, prev, current) {
    console.info(`Changing property: ${property}...`);
    console.info(`From (${prev}) to (${current})`);
});

person.age = 48;
person.age++;
    

Proxies code - II

    
function Observable(target) {
    let observableRegistry = [];

    function triggerPropertyChanged(property, previousValue, newValue) {
        observableRegistry.forEach(callback => {
            callback.call(null, property, previousValue, newValue);
        });
    }

    let proxy = new Proxy(target, {
        set: function(object, property, value, proxy) {
            let previousValue = object[property];
            object[property] = value;
            if ('onPropertyChanged' !== property) {
                triggerPropertyChanged(property, previousValue, value);
            }
            return true;
        }
    });    

    proxy.onPropertyChanged = function(callback) {
        observableRegistry.push(callback);
    };

    return proxy;
}
    

Which features are being used?

Source: Javascript developer survey results (Ponyfoo)

Features ordered by use

arrow functions let and const promises modules classes template literals destructuring rest and spread Collections Iterators Generators Symbols Proxies

references