Babel.js and ES6/7 Syntax

July 17th, 2015

Topics

  • JavaScript ( <= ES5 )
  • New Syntax in ES6/7
  • Helpful Workflow Tools

JavaScript Features (ES5 and worse)

  • callback hell

 

  • verbose prototypical Inheritance

 

  • dynamic scoping

 

  • clunky ancient Array Iteration

 

  • no (baked in) module system
// highway to callback hell
var that = this;
d3.getData(function(x){
     d3.getMoreData(x, function(y){
        d3.getEvenMoreData(y, function(z){ 
            that.process(x, y, z);
        });
    });
});

// what is this, Java!?
EnterpriseGrade.prototype.getStrategyCounters = function(arr) {
    // what is this, C!?
    for (var i = 0, getNums = []; i < arr.length; i++) {
        getNums[i] = function() {
            return arr[i]; // hope this works...
        };
    }
}

System.import('main.js');
define(['foo'], function (foo) {
    var baz = require('baz');
    return function () { foo.doSomething(baz); };
});

New Syntax

improvements to ECMA-262

 

Features

  • Block Scoping

  • Arrow Functions

  • Better Object Literals

  • Destructuring

  • Template Strings

  • Function Parameter Improvements

  • Symbols

  • Iterators

  • Generators

  • Classes

  • Modules

ES6

  • Comprehensions

  • Asynchronous Functions

  • Decorators

  • New Operators

ES7

Babel.js

Do Not Dispair!

ES5 \subset ES6 \subset ES7 \ldots
ES5ES6ES7ES5 \subset ES6 \subset ES7 \ldots

Subsequent JavaScript editions are

Strict Supersets

of their predecessors

ES6 Features

Source

Transpiled ES5

Block scoping (ES6)

const a = 1;
let b = 2;


if (a) {
    let b = 1;
    console.log(b);
    let x = 1;
}

console.log(x) // not defined

a = 2; // static error (immutable value)
let b = 1; // static error (re-declaration)
"use strict";

var a = 1;
var b = 2;

if (a) {
    var _b = 1;
    console.log(_b);
    var _x = 1;
}

console.log(x);

Block Scoping (ES6)

Takeaways

  • no more reason to use var!

  • for (let = ...)
  • no redeclaration!
  • no hoisting!
  • const provides immutable references
  • babel is smart

Source

Transpiled ES5

let obj = {
    // Shorthand for ‘handler: handler’
    handler,
    
    // Methods
    toString() {
     // Super calls
     return "d " + super.toString();
    },
    
    // Computed (dynamic) property names
    [ "prop_" + (() => 42)() ]: 42
};
"use strict";

var _obj;

var _get = function get(_x, _x2, _x3) {...

function _defineProperty(obj, key, value) {...

var obj = _obj = _defineProperty({
    // Shorthand for ‘handler: handler’
    handler: handler,

    // Methods
    toString: function toString() {
        // Super calls
        return "d " + _get(
            Object.getPrototypeOf(_obj), 
            "toString", 
            this
        ).call(this);
    }

}, "prop_" + (function () {
    return 42;
})(), 42);
// Computed (dynamic) property names

Better Object Literals (ES6)

Takeaways

  • {prop}
  • {method() { super.method() }}
  • {[a + b] : 'c'} 
  • { ['me' + 'thod']() { } }

Source

Transpiled ES5

let bob = {
  _name: "Bob",
  nameGetter() {
    return f => this._name;
  }
}

let f = x => x + 1;

let fn = (x, y, z) => { return x + y + z; };

let noop = ()=>{};

let curry = x => y => x + y;

// won't work! must wrap object in parens...
let literal = x => {a : 1};
var bob = {
  _name: "Bob",
  nameGetter: function nameGetter() {
    var _this = this;

    return function (f) {
      return _this._name;
    };
  }
};

var f = function f(x) {
  return x + 1;
};
var fn = function fn(x, y, z) {
  return x + y + z;
};
var noop = function noop() {};

var curry = function curry(x) {
  return function (y) {
    return x + y;
  };
};

Arrow Functions (ES6)

Takeaways

  • lexical this!
  • babel does it the same way we do (var this = _this)
  • parens + braces matter depending on the context

Source

Transpiled ES5

Destructuring (ES6)

let arr = [1, 2, 3, 4, 5];
let w = {
  x : 1, 
  y : { z : [2, 3] }
};

// unpack arrays, with defaults
let [a=2, b, ...rest] = arr;

// unpack object properties, at any level
let {x} = w;

// get fancy
let {
    y : {
        z: [p, q]
    }, 
    y
} = w;    
"use strict";

var _slicedToArray = (function () { ...

var arr = [1, 2, 3, 4, 5];
var w = {
  x: 1,
  y: { z: [2, 3] }
};

// unpack arrays, with defaults
var _arr$0 = arr[0];
var a = _arr$0 === undefined ? 2 : _arr$0;
var b = arr[1];
var rest = arr.slice(2);

// unpack object properties, at any level
var x = w.x;

// get fancy

var _w$y$z = _slicedToArray(w.y.z, 2);

var p = _w$y$z[0];
var q = _w$y$z[1];
var y = w.y;

Destructuring (ES6)

Takeaways

  • var prop = obj.prop, x = obj.p[0] .....
  • let {prop, p :[x, y]} = obj;
  • Much DRY-er extraction of variables

  • extends to function parameters!

Source

Transpiled ES5

app.directive('Test', function() {
  return {
    template : `
      <div class="{{'classy'}}">
        Text
      </div>
    `
  }
})

let imputed = `http://${domain}/?${params}`

let v1 = 1, v2 = 2;

// no parens!
console.log `a${v1}b${v2}c`
// => ["a","b","c"] 1 2
'use strict';

function _taggedTemplateLiteral(strings, raw) { return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }

app.directive('Test', function () {
  return {
    template: '\n      <div class="{{\'classy\'}}">\n        Text\n      </div>\n    '
  };
});

var imputed = 'http://' + domain + '/?' + params;

var v1 = 1,
    v2 = 2;

console.log(
  _taggedTemplateLiteral(
    ['a', 'b', 'c'], ['a', 'b', 'c']
  ), v1, v2
);
// => ["a","b","c"] 1 2

Template Strings (ES6)

Takeaways

  • Multiline strings including whitespace!

  • Nested quotes (of multiple types)

  • Easy inline HTML

  • Variable Interpolation

  • String.raw

Source

Transpiled ES5


function f(a=true, ...rest) {
  return a ? rest.join(", ") : rest;
}

let g = ({x, y}) => {
  x += 1; z += 1;
  return {x, y};
}

let h = (a, b, c) => a + b + c;

let a = [1, 2, 3];
console.log( h(...a) )
"use strict";

function f() {
  var a = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];

  for (var _len = arguments.length, rest = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
    rest[_key - 1] = arguments[_key];
  }

  return a ? rest.join(", ") : rest;
}

function g(_ref) {
  var x = _ref.x;
  var y = _ref.y;

  x += 1;z += 1;
  return { x: x, y: y };
}

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

var a = [1, 2, 3];
console.log(h.apply(undefined, a));

Function Parameter Improvements (ES6)

Takeaways

  •  ...args > Array.prototype.slice.call(arguments)
  • arr.push(...args) > arr.push.apply(arr, args)
  • Default parameters

  • Destructuring
  • (Almost) as nifty as Python **kwargs

Source

Transpiled ES5

Symbols (ES6)

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

// new symbol
var key = Symbol("key");
var c = new MyClass("hello")
console.log(c[key] === undefined)
"use strict";

var MyClass = (function () {

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

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

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

  return MyClass;
})();

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

Symbols (ES6)

Takeaways

  • (Essentially) Private methods / attributes, useful for collision prevention

  • Special Symbols (Symbol.iterator) 

Source

Transpiled ES5

Iterators (ES6)

let fibonacci = {
  [Symbol.iterator]() {
    let pre = 0, cur = 1;
    return {
      next() {
        [pre, cur] = [cur, pre + cur];
        return { done: false, value: cur }
      }
    }
  }
}
"use strict";

function _defineProperty(obj, key, value) {...

var fibonacci = _defineProperty(
    {}, 
    Symbol.iterator, 
    function () {
      var pre = 0,
          cur = 1;
      return {
        next: function next() {
          var _ref = [cur, pre + cur];
          pre = _ref[0];
          cur = _ref[1];
    
          return { done: false, value: cur };
        }
      };
    }
);

Iterators (ES6)

Takeaways

  • Iterators are any object which has a method next which is defined as...

    next -> {done : <bool>, value : <any>}
  • Objects with a [Symbol.iterator] method are iterable (Array, Set, Map, arguments, String)

Source

Transpiled ES5

for (let n of fibonacci) {
  if (n > 1000)
    break;
  console.log(n);
}
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;

try {
  for (var _iterator = fibonacci[Symbol.iterator](), _step; 
       !(_iteratorNormalCompletion = (_step = _iterator.next()).done); 
       _iteratorNormalCompletion = true) {
    var n = _step.value;

    // truncate the sequence at 1000
    if (n > 1000) break;
    console.log(n);
  }
} catch (err) {
  _didIteratorError = true;
  _iteratorError = err;
} finally {
  try {
    if (!_iteratorNormalCompletion && _iterator["return"]) {
      _iterator["return"]();
    }
  } finally {
    if (_didIteratorError) {
      throw _iteratorError;
    }
  }
}

Iterators (ES6)

Takeaways

  • for (let item of iterable)...
    
  • performance concerns? (new object on each step)

Source

Transpiled ES5

Generators (ES6)

function* naturalNumbers() {
  let n = 1;
  while (true) yield n++; 
}

for (let n of naturalNumbers()) {
  if (n > 20) {
    break;
  }
  console.log(n);
}
var marked0$0 = [naturalNumbers].map(regeneratorRuntime.mark);
function naturalNumbers() {
  var n;
  return regeneratorRuntime.wrap(function naturalNumbers$(context$1$0) {
    while (1) switch (context$1$0.prev = context$1$0.next) {
      case 0:
        n = 1;

      case 1:
        if (!true) {
          context$1$0.next = 6;
          break;
        }

        context$1$0.next = 4;
        return n++;

      case 4:
        context$1$0.next = 1;
        break;

      case 6:
      case "end":
        return context$1$0.stop();
    }
  }, marked0$0[0], this);
}

Generators (ES6)

Takeaways

  • Generators return Iterables

  • Babel slices the function context between the yield statements and produces an FSM

Source

Transpiled ES5

Classes (ES6)

class SkinnedMesh extends THREE.Mesh {
  constructor(geometry, materials) {
    super(geometry, materials);
    this.idMatrix = (
      SkinnedMesh.defaultMat()
    );
  }
  update(camera) {
    super.update();
  }
  static defaultMat() {
    return new THREE.Matrix4();
  }
}
var _createClass = (function () {...
var _get = function get(_x, _x2, _x3) {...
function _classCallCheck(instance, Constructor) {...
function _inherits(subClass, superClass) {...

var SkinnedMesh = (function (_THREE$Mesh) {
  _inherits(SkinnedMesh, _THREE$Mesh);

  function SkinnedMesh(geometry, materials) {
    _classCallCheck(this, SkinnedMesh);

    _get(Object.getPrototypeOf(SkinnedMesh.prototype), "constructor", this).call(this, geometry, materials);
    this.idMatrix = SkinnedMesh.defaultMat();
  }

  _createClass(SkinnedMesh, [{
    key: "update",
    value: function update(camera) {
      _get(Object.getPrototypeOf(SkinnedMesh.prototype), "update", this).call(this);
    }
  }], [{
    key: "defaultMat",
    value: function defaultMat() {
      return new THREE.Matrix4();
    }
  }]);

  return SkinnedMesh;
})(THREE.Mesh);

Classes (ES6)

Takeaways

  • Classes are just sugar (concise and convenient sugar

  • Easy access to prototype's methods and constructor

Source

Transpiled ES5

Modules (ES6)

import _ from 'lodash';

import {a, b, c} from './mycode';

_.map(a, b);

export const one = 1;

export default function main() {}
exports.__esModule = true;
var _temporalUndefined = {};
var one = _temporalUndefined;

exports['default'] = main;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

function _temporalAssertDefined(val, name, undef) { if (val === undef) { throw new ReferenceError(name + ' is not defined - temporal dead zone'); } return true; }

var _lodash = require('lodash');

var _lodash2 = _interopRequireDefault(_lodash);

var _mycode = require('./mycode');

_lodash2['default'].map(_mycode.a, _mycode.b);

exports.one = one = 1;
exports.one = _temporalAssertDefined(one, 'one', _temporalUndefined) && one;

function main() {}

Modules (ES6)

Takeaways

  • Named / default exports

  • destructuring in imports
  • Babel / CommonJs interop

ES7 Features

Source

Transpiled ES5

Comprehensions (array) (ES7)

let evens = [
  for (n of [1,2,3,4,5,6])
    if (n%2 === 0)
     n
]
"use strict";

var evens = (function () {
  var _evens = [];
  var _arr = [1, 2, 3, 4, 5, 6];

  for (var _i = 0; _i < _arr.length; _i++) {
    var n = _arr[_i];

    if (n % 2 === 0) {
      _evens.push(n);
    }
  }

  return _evens;
})();

Source

Transpiled ES5

Comprehensions (generator) (ES7)

let evens = ( // <- note parens
  for (n of [1,2,3,4,5,6])
    if (n%2 === 0)
     n
)
var _this = this;
var evens = regeneratorRuntime.mark(function callee$0$0() {
  var _arr, _i, n;

  return regeneratorRuntime.wrap(function callee$0$0$(context$1$0) {
    while (1) switch (context$1$0.prev = context$1$0.next) {
      case 0:
        _arr = [1, 2, 3, 4, 5, 6];
        _i = 0;

      case 2:
        if (!(_i < _arr.length)) {
          context$1$0.next = 10;
          break;
        }

        n = _arr[_i];

        if (!(n % 2 === 0)) {
          context$1$0.next = 7;
          break;
        }

        context$1$0.next = 7;
        return n;

      case 7:
        _i++;
        context$1$0.next = 2;
        break;

      case 10:
      case "end":
        return context$1$0.stop();
    }
  }, callee$0$0, _this);
})();

Comprehensions (ES7)

Takeaways

  • only for ... of and if are allowed

  • (...) -> generator, [...] -> array

  • need double parens if doing a generator comprehension in a function call ->        fn(( for (c of ...)  ... ))

  • Multiple for or if statements ok
  • Iterator performance concerns
let evens = (
  for (n of [1,2,3,4,5,6])
    for (j of [1,2,3])
      for (z of [5,6,7])
        if (j > 6)
          n 
)

Source

Transpiled ES5

async function get() {
  let val = await Promise.resolve(1);
  console.log(val);
  return val;
}

get().then(v => console.log(v + 1));

let getter = async() => { /*...*/ }
"use strict";

function get() {
  var val;
  return regeneratorRuntime.async(function get$(context$1$0) {
    while (1) switch (context$1$0.prev = context$1$0.next) {
      case 0:
        context$1$0.next = 2;
        return regeneratorRuntime.awrap(Promise.resolve(1));

      case 2:
        val = context$1$0.sent;

        console.log(val);
        return context$1$0.abrupt("return", val);

      case 5:
      case "end":
        return context$1$0.stop();
    }
  }, null, this);
}

get().then(function (v) {
  return console.log(v + 1);
});

Async Functions (ES7)

Takeaways

  • Async functions return Promises

  • Babel executes them via a Iterator FSM, calling next on resolution of await statements

  • await* [...] === await Promise.all([....])
  • Try / Catch works like synchronous code

  • Issues with Angular 1.0 $q  (see https://github.com/angular/angular.js/issues/6697)

Source

Transpiled ES5

Decorators (ES7)

@annotationFactory(true)
@nameThis
class MyClass { }

let nameThis = target => {
    target.named = 'target';
};

let annotationFactory = value => target => {
  target.annotated = value;
};
'use strict';

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }

var MyClass = (function () {
  function MyClass() {
    _classCallCheck(this, _MyClass);
  }

  var _MyClass = MyClass;
  MyClass = nameThis(MyClass) || MyClass;
  MyClass = annotationFactory(true)(MyClass) || MyClass;
  return MyClass;
})();

var nameThis = function nameThis(target) {
  return target.named = 'target';
};

var annotationFactory = function annotationFactory(value) {
  return function (target) {
    target.annotated = value;
  };
};

Decorators (ES7)

Takeaways

  • Simply functions which wrap a class upon definition

  • can be function factories
  • Execution order is bottom up

Bonus ES7

Features in Babel

  • ::console.log === console.log.bind(console)
  • obj::method()::other() === other.call(method.call(object))
  • Map, Set, WeakMap, WeakSet
  • a**b === Math.pow(a, b)

Babel Workflow

Final Thoughts

New JavaScript Syntax

By Ben Southgate

New JavaScript Syntax

A walkthrough of syntax changes in ES6 + ES7 made available now through Babel.js

  • 905