es6-presentation-3



es6-presentation-3

0 0


es6-presentation-3


On Github tdoherty / es6-presentation-3

ECMAScript 6 In Depth

Who Am I?

Tim Doherty

Software Architect at

Previously in ECMAScript 6 In Depth

  • ES6 is the first major language update in over 15 years
  • New syntax aimed at reducing boilerplate
  • JavaScript now has "classes" (but not really)
  • Native modules for organizing code
  • Enhancements to Object, Array, and String built-ins
  • New Symbol primitive type
  • Iterators + for...of
  • Generators

Part III

  • Enhanced Built-ins
    • Number
    • Math
    • RegEx
  • Data Structures
    • Set/WeakSet
    • Map/WeakMap
  • Reflect API
  • Proxies
  • Tail call optimization
  • Promises

Number Literal Extensions

Explicit literal forms for octal and binary:

  • var oct = 0o52; // or 0O52
  • var bin = 0b101010; // or 0B101010

Number Constants

  • Number.EPSILONThe smallest interval between 2 representable numbers
  • Number.MAX_SAFE_INTEGERThe maximum safe integer in JavaScript (253 - 1)
  • Number.MIN_SAFE_INTEGERThe minimum safe integer in JavaScript (-(253 - 1))
parseInt, parseFloat, isFinite, isInteger, isSafeInteger

Number Methods

  • Number.isNaN()
  • Number.isFinite()
  • Number.isInteger()
  • Number.isSafeInteger()
  • Number.parseInt()
  • Number.parseFloat()

Math

Most additions to Math are intended for transpile-to-JS systems, like Emscripten

Hyperbolic Variants

  • Math.acosh()
  • Math.asinh()
  • Math.atanh()
  • Math.cosh()
  • Math.sinh()
  • Math.tanh()

Math methods

  • cbrt
  • clz32
  • expm1
  • fround
  • hypot
  • imul
  • log10
  • log1p
  • log2
  • sign
  • trunc
Many are C/C++ oriented, not enough time to cover here

Regex

ES6 introduces two new flags, and three new data properties

Regex Flags

/y - sticky flag, anchors search at last index

/u - unicode mode, treats surrogate pairs as a single character

const TOKEN_G  = /\s*(\-|[0-9]+)\s*/g;
const TOKEN_GY = /\s*(\-|[0-9]+)\s*/gy;

function tokenize(TOKEN_REGEX, str) {
  var result = [], match;
  while (match = TOKEN_REGEX.exec(str)) {
    result.push(match[1]);
  }
  return result;
}

tokenize(TOKEN_G, '1-2');   // ['1', '-', '2']
tokenize(TOKEN_GY, '1-2');  // ['1', '-', '2']
tokenize(TOKEN_G, '1x-2');  // ['1', '-', '2']
tokenize(TOKEN_GY, '1x-2'); // ['1']
    '\uD83D\uDCA9' // 💩 U+1F4A9 PILE OF POO

    /\uD83D/.test('\uD83D\uDCA9')
    // true

    /\uD83D/u.test('\uD83D\uDCA9')
    // false

    /\uD83D\uDCA9/u.test('\uD83D\uDCA9')
    // true

    /\u{1F4A9}/u.test('\uD83D\uDCA9')
    // true

    

Regex Prototype Properties

flags: the flags used when searching

          /xyz/ig.flags // 'gi'
        

sticky: was the sticky flag specified

          /xyz/y.sticky // true
        

unicode: was the unicode flag specified

          /xyz/u.unicode // true
        

Data Structures

ES6 introduces new data structures, Set and Map

Garbage collection-friendly variants, WeakSet and WeakMap

Set

A collection of unique values of any type

Similar to array, except values are unique and not indexed

        let s = new Set();

        s.add(1);    // Set {1}
        s.add(2);    // Set {1, 2}
        s.add(3);    // Set {1, 2, 3}
        s.size;      // 3
        s.has(3);    // true
        
        s.add(3);    // Set {1, 2, 3}
        s.delete(3); // Set {1, 2}
        s.has(3);    // false
        s.clear();   // Set {}
        s.size;      // 0
    
        // optional iterable argument
        let s = new Set([1, 2, 3, 4]);

        s.forEach(value => {}); 
        for (let item of s) {}
        let a = [...s] // [1, 2, 3, 4]
    

Map

Simple key/value map, any value can be used as key or value

Map vs. Object

Objects have been used as maps historically,but there are key advantages to Map

  • An Object has a prototype, thus default keys in the map
  • Object keys are Strings, but can be any value for a Map
  • Map.size vs. manually tracking an Object's size
  • Map iterates its elements in insertion order
    let m = new Map();
    let strKey = 'stringy';
    let objKey = {};
    let fnKey = () => {};

    m.set(strKey, `value for 'stringy'`);
    m.set(objKey, 'value for objKey');
    m.set(fnKey, 'value for fnKey');
    m.size            // 3

    m.get(strKey);    // "value for 'stringy'"
    m.get(objKey);    // "value for objKey"
    m.get(fnKey);     // "value for fnKey"

    m.get('stringy'); // "value for 'stringy'"
    m.get({});        // undefined
    m.get(() => {});  // undefined
    
    let m = new Map();

    m.set(NaN, 'Not a Number');
    m.get(NaN) // "Not a Number"

    let otherNaN = Number('foo');
    m.get(otherNaN) // "Not a Number"

    
    // optional iterable argument
    let m = new Map([
        ['k1', 'v1'],
        ['k2', 'v2']
    ]);

    for (let [key, value] of m) {}
    for (let key of m.keys()) {}
    for (let value of m.values()) {}
    for (let [key, value] of m.entries) {}
    m.forEach((key, value) => {});
    [...m] // [['k1', 'v1'], ['k2', 'v2']]
    

WeakSet

A WeakSet is collection of weakly held objects

There aren't many use cases, but one is marking objects

    let processedObjects = new WeakSet();

    function processObject(obj) {
      // ... do processing
      processedObjects.add(obj);
      return obj;
    }

    function isProcessed(obj) {
      return processedObjects.has(obj);
    }
    

WeakMap

  • Simple key-value map where the key is a weakly-held object
  • Primary use case is mapping values to objects that might disappear later, such as DOM elements
let wm = new WeakMap();
let el = document.querySelector('.element');

wm.set(el, 'el data');
wm.has(el); // true
wm.get(el); // "el data"

el.parentNode.removeChild(el);
el = null   // remove local reference*
wm.has(el); // false
wm.get(el); // undefined
*this is sort of cheating, we can't look up value by 'null' and el is no longer the key we used to insert
    const privateData = new WeakMap();

    class Classy {
      constructor(prop1, prop1) {
        privateData.set(this. {
          prop1: prop1,
          prop2: prop2
        });
      }
      get prop1() {
        return privateData.get(this).prop1;
      }
      get prop2() {
        return privateData.get(this).prop2;
      }
    }
    

Reflect API

Module-friendly reflection API, mostly migrated from Object

Most methods map one-to-one onto Proxy traps, up next

Reflect Static Methods

  • getPrototypeOf()
  • setPrototypeOf()
  • isExtensible()
  • preventExtensions()
  • getOwnPropertyDescriptor()
  • defineProperty()
  • has()
  • get()
  • set()
  • deleteProperty()
  • enumerate()
  • ownKeys()
  • apply()
  • construct()

Proxies

Define custom behavior for some operations on an object

can't be transpiled

Reflective meta-programming:

  • Introspection: read-only access to the structure of a program
    • Object.keys()
  • Self-modification: you can change that structure
    • Object.defineProperty()
  • Intercession: redefine the semantics of some operations.
    • ?

Enter the Proxy Object

Proxy adds intercession to JavaScript

A proxy instance is created with the following parameters:

  • handler: object whose methods - or "traps" - intercept operations on a target
  • target: object which the proxy virtualizes. Operations without traps fall back to the target
traps are analogous to those in operating systems

Use Cases

  • Validation
  • Tracing
  • Local/remote methods
  • Profiling
  • Data access objects
  • Membranes
  • ...and much more

Handler Methods (traps)

  • getPrototypeOf()
  • setPrototypeOf()
  • isExtensible()
  • preventExtensions()
  • getOwnPropertyDescriptor()
  • defineProperty()
  • has()
  • get()
  • set()
  • deleteProperty()
  • enumerate()
  • ownKeys()
  • apply()
  • construct()

All traps are optional, default is to forward to the target

    let handler = {
      get(target, property, receiver) {
        return property in target ?
          target[property] :
          42;
      }
    };

    let p = new Proxy({foo: 'bar'}, handler);

    p.foo            // 'bar'
    p.meaningOfLife  // 42
    
    let validator = {
      set(target, property, value, receiver) {
        if (property === 'year' &&
            !Number.isInteger(value)) {
          throw new TypeError();
        }
        Reflect.set(target, property, value);
      }
    };

    let car = new Proxy({}, validator);

    car.year = 1963  // 1963
    car.year = 'foo' // TypeError
    
let tracer = {
  get(target, trapName, receiver) {
    return function (...args) {
      console.log(trapName.toUppercase() +
                  ' ' +
                  args.slice(1));
      return Reflect[trapName](...args);
    }
  }
};

let p = new Proxy({}, tracer);

p.foo = 'bar' // SET foo,'bar',[object Object]
p.foo         // GET foo,[object Object]
let membrane = {
  get(target, trapName, receiver) {
    return function (...args) {
      // ...threat mitigation
      return Reflect[trapName](...args);
    }
  }
};

let rev = Proxy.revocable(jQuery, membrane);
let jqMembrane = rev.proxy;

jqMembrane.ajax(/*...*);
// ...when done
rev.revoke()
jqMembrane.ajax(/*...*); // TypeError

Tail Call Optimization

Execute functions in tail position without growing the call stack

Effectively use recursion as a tool, with stack frame reuse

Only works in strict mode

Proper Tail Calls

Last thing a function does is return the result of a function call

    "use strict";

    function baz(bim) {
      return bim + 1;
    }

    function foo(bar) {
      bar += 1;
      return baz(bar); <-PTC
    }

    function foo(bar) {
      bar += 1;
      return 1 + baz(bar); <-!PTC
    }
    
"use strict";

function baz(bim) {
  return bim + 1; // A
}

function foo(bar) {
  bar += 1;
  return baz(bar); // B
}

foo(1); // C
  • foo = function () {...}
  • bar = function () {...}
"use strict";

function baz(bim) {
  return bim + 1; // A
}

function foo(bar) {
  bar += 1;
  return baz(bar); // B
}

foo(1); // C
  • bar = 2
  • Line C
  • foo = function () {...}
  • bar = function () {...}
"use strict";

function baz(bim) {
  return bim + 1; // A
}

function foo(bar) {
  bar += 1;
  return baz(bar); // B
}

foo(1); // C
  • bim = 3
  • Line B
  • bar = 2
  • Line C
  • foo = function () {...}
  • bar = function () {...}
"use strict";

function baz(bim) {
  return bim + 1; // A
}

function foo(bar) {
  bar += 1;
  return baz(bar); // B
}

foo(1); // C
  • bar = 2
  • Line C
  • foo = function () {...}
  • bar = function () {...}
"use strict";

function baz(bim) {
  return bim + 1; // A
}

function foo(bar) {
  bar += 1;
  return baz(bar); // B
}

foo(1); // C
  • foo = function () {...}
  • bar = function () {...}
"use strict";

function baz(bim) {
  return bim + 1; // A
}

// just prior to baz()
function foo(bar) {
  bar += 1;
  return baz(bar); // B
}

foo(1); // C
  • bim = 3
  • Line C
  • foo = function () {...}
  • bar = function () {...}
function factorial(n) {
  return facHelper(n, 1);
}

function facHelper(n, acc) {
  if (n == 1) {
    return acc;
  } else {
    return facHelper(n-1, n * acc); <-PTC
  }
}

factorial(1); // 1
factorial(5); // 120
function forEach(arr, cb, start = 0) {
  if (0 <= start && start < arr.length) {
    callback(arr[start], start, arr);
    return forEach(arr, cb, start+1); <-PTC
  }
}

forEach(['a', 'b'], (el, i) => {
  console.log(`${i}. ${el}`)
});

// 1. a
// 2. b

Promises

ES6 introduces a native Promise object

Promise API eases deferred and asynchronous computations

Influenced by libraries / frameworks like jQuery, Q, AngularJS

Promise Methods

  • Promise.resolve(value)
  • Promise.reject(reason)
  • Promise.all(iterable)
  • Promise.race(iterable)

Promise.prototype Methods

  • Promise.prototype.then(onResolved, onRejected)
  • Promise.prototype.catch(onRejected)
    let p = new Promise((resolve, reject) => {
      resolve('foo');
    });

    p.then(value => {
      console.log(value); // "foo"
    });
    
function asyncFunc1() {
  return new Promise((resolve, reject) => {
    //...
  });
}

asycFunc1()
  .then(asyncFunc2)
  .then(asyncFunc3)
  .then(asyncFunc4)
  .then(asyncFunc5)
  .catch((reason) => {...});
function httpGet(url) {
  return new Promise((resolve, reject) => {
    var request = new XMLHttpRequest();
    request.onreadystatechange = () => {
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    }
    request.onerror = () => {
      reject(new Error(this.statusText));
    };
    request.open('GET', url);
    request.send();
  });
}
Fetch API
httpGet('http://somesite.com')
  .then(
    value => { //success },
    reason => { // error }
  );
let promises = [
  'http://somesite.com/path1',
  'http://somesite.com/path2
].map(httpGet);

Promise.all(promises)
  .then(values => {
    values.forEach(value => {
      // do something on success
    });
  })
  .catch(reason => {
    // gets first rejection
  );

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms);
  });
}

Promise.race([
  httpGet('http://somesite.com'),
  timeout(5000).then(() => {
    throw new Error('Timed out')
  })
])
.then(value => {...})
.catch(reason => {...});

Promises With Async Generators

// from part II
function req(url) {
  doAjax(url, function (response) {
    it.next(response);
  });
}

function* async() {
  let r1 = yield req('/req1');
  let r2 = yield req(`/req2?id=${r1.id}`);
  console.log(`r2: ${r2}`);
}

var it = async();
it.next(); // kick it off

... we can do better, with promises
function spawnGenerator(g) {
  var it = g(), ret;

  (function iterate(val){
    ret = it.next(val);

    if (!ret.done) {
      if ('then' in ret.value) {
        ret.value.then(iterate);
      } else {
        setTimeout(() => {
          iterate(ret.value);
        }, 0);
      }
    }
  })();
}
"poor man's promise check" and spawn code form Kyle Simpson
function* async() {
  // use httpGet, which returns a promise
  let r1 = yield httpGet('/req1');
  let r2 = yield httpGet(`/req2?id=${r1.id}`);
  console.log(`r2: ${r2}`);
}

spawnGenerator(async);

Wrapping Up

  • ECMAScript 6 consists of three main types of updates
    • New syntax
      • New ways to achieve existing functionality
    • Enhanced standard library
      • Additional functionality for built-in objects
    • Entirely new features
      • Some of these, like TCO and Proxy, can't be transpiled

ECMAScript 6 is now the language standard

Browsers are continually implementing features

Writing/transpiling ES6 now, will help push browser vendors

What's Next

EcmaScript 2016 (ES7)

  • async / await
  • Object.observe()
  • Object rest/spread properties
  • Array comprehensions
  • and more...

Go Forth and Write ES6!

Resources

Thanks!

Tim Doherty

@TimCDoherty

timdoherty.net