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
-
ECMAScript 7 Proposal (various levels)
- Stage 0 - Strawman (3 in babel)
- Stage 1 - Proposal (5)
- Stage 2 - Draft (1)
- Stage 3 - Candidate (none yet)
- Stage 4 - Finished (none yet)
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!
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
Better Object Literals (ES6)
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
Arrow Functions (ES6)
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
Template Strings (ES6)
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 -> {done : <bool>, value : <any>}next
which is defined as... -
Objects with a [
Symbol.iterator
] method are iterable (Array, Set, Map, arguments, String
)
Source
Transpiled ES5
Iterators (continued) (ES6)
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
andif
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 Functions (ES7)
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 ofawait
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
- node-source-map-support : package which can display babel generated source maps in node error stack
-
babel-eslint : Use Babel for linting of new syntax
- ES6 Syntax highlighting : (babel-sublime, vim + atom have built in support)
- Custom syntax transformation : https://babeljs.io/docs/advanced/plugins/
Final Thoughts
- Performance and Size
- http://babeljs.io/docs/advanced/loose/
- http://babeljs.io/docs/usage/runtime/
New JavaScript Syntax
By Ben Southgate
New JavaScript Syntax
A walkthrough of syntax changes in ES6 + ES7 made available now through Babel.js
- 1,099