On Github hiddentao / es6-slides
By Ramesh Nair and Grégoire Charvet
Grégoire Charvet (geekingfrog.com)
Ramesh Nair (hiddentao.com)
For chrome, need to enable the experimental js features
Node.js: get the latest 0.11 and add the flag --harmony
Support is the same as chrome (V8)
Almost the same as IE (so quasi inexistant)
Finally !
for(var i=10; i>0 ; i--) { // do stuff with i } console.log(i); // 0
for(let i=10; i>10; i--) { } console.log(i); // `i is not defined`
var log = function(msg) {}; if(someCond) { let log = Math.log; // do some stuff } log("I'm done here");
broken example
var a = ['rhum', 'banana', 'nutella']; for(var i = 0, n=a.length; i<n; i++) { var nomnom = a[i]; setTimeout(function() { console.log(nomnom); }, 500*(i+1)) }
var a = ['rhum', 'banana', 'nutella']; for(var i = 0, n=a.length; i<n; i++) { let nomnom = a[i]; setTimeout(function() { console.log(nomnom); }, 500*(i+1)) }
Like let, but define read-only constant declarations.
'use strict'; const i = 10; i = 5; // throw error
// Assignment var [day, month, year] = [19, 02, 2014]; // Swap two values. var x=3, y=4; [x, y] = [y, x];
var now = function() { return [19, 02, 2014]; } var [day, month] = now(); var [, , year] = now();
var now = function() { return { d: 19, m: 2, y: 2014 }} var {d: day, m: month} = now(); // day: 19 // month: 2
recipes = [ { name: 'burger', calorie: 215 }, { name: 'pizza', calorie: 266 } ]; recipes.forEach(function ({ name: name, calorie: calorie }) { console.log(name, calorie); } );
No more:
function(a) { if(!a) { a = 10; } // do stuff with a }
function(a=10) { // do stuff with a }
undefined will trigger the evaluation of the default value, not null
function point (x, y=1, z=1) { return console.log(x, y, z); } point(10, null); // 10, null, 1
Number of parameters without default value
(function(a){}).length // 1 (function(a=10){}).length // 0 (function(a=10, b){}).length // 1
function(name) { console.log(name); arguments[0] = 'ME !'; console.log(name); // ME ! Array.isArray(arguments); // false }
function(...args) { Array.isArray(args); // true // do some stuff } function(name, ...more) { }
var humblify = function(name, ...qualities) { console.log('Hello %s', name); console.log('You are '+qualities.join(' and ')); } humblify('Greg', 'awesome', 'the master of the universe'); // Hello Greg // You are awesome and the master of the universe
Rest parameters can only be the last parameter
// incorrect function(...args, callback) { }
Rest parameter is always an array
function f(name, ...more) { Array.isArray(more); // always true return more.length; } f(); // returns 0
Does not include the rest parameter
(function(a) {}).length // 1 (function(a, ...rest) {}).length // 1
Usecase: create new array with an existing one inside it:
var from = [1, 2]; // wants: [0, 1, 2, 3] ie [0, from, 3]
a.unshift(0); a.push(3); // and splice is here also
var total = [0, ...from, 3];
var fake = { 0: 'I am', 1: 'not', 2: 'an aray', length: 3 };
Before:
var nodes = document.querySelectorAll('p'); var nodes = [].slice.call(nodes);
nodes = [...nodes];
Better way:
Array.from(document.querySelectorAll('p'));
Out of the scope of the talk.
var f = function(one, two, three) {} var a = [1, 2, 3]; f.apply(null, a);
function f() { for(let i=0; i<arguments.length; ++i) console.log(arguments[i]); } f.apply(this, ['one', 2, 'foo']); // one // 2 // foo
var f = function(one, two, three) {} var a = [1, 2, 3]; f(...a);
var f = function(a, b, c, d, e, f) {}; var a = [1, 2]; f(-1, ...a, 3, ...[-3, -4]);
With es5, one cannot use apply with new.
var Constructor = function() { // do some stuff } var c = new Constructor.apply({}, []); //invalid
var dataFields = readDateFields(database); var d = new Date(...dateFields);
To push multiple elements:
var a = []; var toPush = [1, 2, 3]; a.push.apply(a, toPush);
a.push(...toPush);
An iterator lets you iterate over the contents of an object.
In ES6, an iterator is an object with a next() method which returns {done, value} tuples.
An iterable is an object which can return an iterator.
Browser: Only Firefox Nightly 29 for now. Firefox stable supports older syntax and semantics. Node: for-of partially supportedArrays are iterable:
var a = [1,2,3], i = a.iterator(); console.log(i.next()); // {done: false, value: 1} console.log(i.next()); // {done: false, value: 2} console.log(i.next()); // {done: false, value: 3} console.log(i.next()); // {done: true, value: undefined}
The for-of loop can be used to simplify iterations:
var a = [1,2,3]; for (var num of a) { console.log(num); // 1, 2, 3 }
Array comprehensions:
var a = [ { color: 'red' }, { color: 'blue' } ]; [ x.color for (x of a) if ('blue' === x.color) ] // [ 'blue' ]* Only in Firefox for now. Not even Node supports it.
We can make any object iterable:
function ClassA() { this.elements = [1, 2, 3]; }
By adding the @@iterator method:
ClassA.prototype['@@iterator'] = function() { return { elements: this.elements, index: 0, next: function() { if (this.index >= this.elements.length) return { done: true, value: undefined }; else return { done: false, value: this.elements[this.index++] }; }}};
We can iterate over the Object:
var col = new ClassA(); for (var num of col) { console.log(num); // 1, 2, 3 }
A generator is a special type of iterator.
A generator provides a throw() method. Its next() method accepts a parameter.
A generator function acts as a constructor for a generator.
Generators offer a clean way of doing asynchronous programming!
Simple example:
var helloWorld = function*() { yield 'hello'; yield 'world'; } var hw = helloWorld(); console.log( hw.next() ); // { value: 'hello', done: false } console.log( hw.next() ); // { value: 'world', done: false } console.log( hw.next() ); // { value: undefined, done: true }
Passing values back to generator:
var helloWorld = function*() { var nextWord = yield 'hello'; yield nextWord; } var hw = helloWorld(); console.log( hw.next() ); // { value: 'hello', done: false } console.log( hw.next('world') ); // { value: 'world', done: false } console.log( hw.next() ); // { value: undefined, done: true }
Let's take it step-by-step to see how code gets suspended...
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.next('world') ); console.log( hw.next() );* Same logic as before, just with more logging
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.next('world') ); console.log( hw.next() );
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.next('world') ); console.log( hw.next() );* As soon as the VM sees that it's a generator function it returns without executing the function body.
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.next('world') ); console.log( hw.next() );
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.next('world') ); console.log( hw.next() );
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.next('world') ); console.log( hw.next() );
Yield 1...
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.next('world') ); console.log( hw.next() );
Yield 1...
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.next('world') ); console.log( hw.next() );
Yield 1... { done: false, value: 'hello' }
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.next('world') ); console.log( hw.next() );
Yield 1... { done: false, value: 'hello' }
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.next('world') ); console.log( hw.next() );
Yield 1... { done: false, value: 'hello' }
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.next('world') ); console.log( hw.next() );
Yield 1... { done: false, value: 'hello' } Yield 2...
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.next('world') ); console.log( hw.next() );
Yield 1... { done: false, value: 'hello' } Yield 2...
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.next('world') ); console.log( hw.next() );
Yield 1... { done: false, value: 'hello' } Yield 2... { done: false, value: 'world' }
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.next('world') ); console.log( hw.next() );
Yield 1... { done: false, value: 'hello' } Yield 2... { done: false, value: 'world' }
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.next('world') ); console.log( hw.next() );
Yield 1... { done: false, value: 'hello' } Yield 2... { done: false, value: 'world' } No more yields...
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.next('world') ); console.log( hw.next() );
Yield 1... { done: false, value: 'hello' } Yield 2... { done: false, value: 'world' } No more yields... { done: true, value: undefined }
The code in the generator doesn't start executing until you say so.
When the yield statement is encountered it suspends execution until you tell it to resume.
What about throw()-ing errors?
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.throw('Voldemort') ); console.log( hw.next() );
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.throw('Voldemort') ); console.log( hw.next() );* As soon as the VM sees that it's a generator function it returns without executing the function body.
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.throw('Voldemort') ); console.log( hw.next() );
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.throw('Voldemort') ); console.log( hw.next() );
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.throw('Voldemort') ); console.log( hw.next() );
Yield 1...
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.throw('Voldemort') ); console.log( hw.next() );
Yield 1...
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.throw('Voldemort') ); console.log( hw.next() );
Yield 1... { done: false, value: 'hello' }
var helloWorld = function*() { console.log('Yield 1...'); var nextWord = yield 'hello'; console.log('Yield 2...'); yield nextWord; console.log('No more yields...'); } var hw = helloWorld(); console.log( hw.next() ); console.log( hw.throw('Voldemort') ); console.log( hw.next() );
Yield 1... { done: false, value: 'hello' } Error: Voldemort
How do Generators make asynchronous programming easier?
The old-school way:
var readFile = function(fileName, cb) { ... }; var main = function(cb) { readFile('file1', function(err, contents1) { if (err) return cb(err); console.log(contents1); readFile('file2', function(err, contents2) { if (err) return cb(err); console.log(contents2); cb(); }); }); } main(console.error);
Improved using Promises:
var readFile = Promise.promisify(function(fileName, cb) { ... }); var main = function() { return readFile('file1') .then(function(contents) { console.log(contents); return readFile('file2'); }) .then(function(contents) { console.log(contents); }) .catch(console.error); } main();Only need to handle errors in one place Much more readable code. Control flow more obvious.
We can do better with generators.
But first we need a function which will automatically handle the yield-ed values and call next() on the generator...
Automatically resolve Promises and call next():
var runGenerator = function(generatorFunction) { var gen = generatorFunction(); var gNext = function(err, answer) { if (err) return gen.throw(err); var res = gen.next(answer); if (!res.done) { Promise.resolve(res.value) .then(function(newAnswer) { gNext(null, newAnswer); }) .catch(gNext); } }; gNext(); }* This assumes that the generator always yields either Promises or values which can be wrapped as a Promise.
Now we can rewrite main() as a generator function:
var readFile = Promise.promisify(function(fileName, cb) { ... }); var main = function*() { try { console.log( yield readFile('file1') ); console.log( yield readFile('file2') ); } catch (err) { console.error(err); } } runGenerator(main);The whole method is much cleaner-looking and easier to understand. We can now use try-catch to catch your errors. Nice. * Using generators allows you to write clean-looking asynchronous code which almost looks synchronous.
You don't need to write runGenerator() yourself.
https://github.com/visionmedia/co - similar to runGenerator but more powerful.
https://github.com/petkaantonov/bluebird - kick-ass Promise library and provides runGenerator-like methods.
Generator delegation:
var inner = function*() { try { yield callServer1(); yield callServer2(); } catch (e) { console.error(e); } }; var outer = function*() { yield* inner(); }; runGenerator(outer);
var outer = function*() { try { yield callServer1(); yield callServer2(); } catch (e) { console.error(e); } }; runGenerator(outer);* Generator delegation is a convenience notation which makes it easy to compose generators
Generator comprehension:
(for (x of a) for (y of b) x * y)
(function* () { for (x of a) { for (y of b) { yield x * y; } } }())* Might seem weird, but has its reasons: http://esdiscuss.org/topic/why-do-generator-expressions-return-generators
Generators = future of JS asynchronous programming.
...and ES6 also has Promises!
http://spion.github.io/posts/why-i-am-switching-to-promises.html
* Native Promises - http://www.html5rocks.com/en/tutorials/es6/promises/var items = new Set(); items.add(5); items.add("5"); items.add(5); console.log(items.size); // 2 var items = new Set([1,2,3,4,5,5,5]); console.log(items.size); // 5Note that value are not coerced - so "5" is considered different to 5. Browsers: Firefox only. Chrome support is behind a flag. Node: Suppored behind harmony flag, though note that add() needs to be used - constructor parameter does nothing. http://www.nczonline.net/blog/2012/09/25/ecmascript-6-collections-part-1-sets/
Modifying a Set
var items = new Set([1,2,3,4,4]); console.log( items.has(4) ); // true console.log( items.has(5) ); // false items.delete(4); console.log( items.has(4) ); // false console.log( items.size ); // 3 items.clear(); console.log( items.size ); // 0
Iterate over a Set using for-of
var items = new Set([1,2,3,4,4]); for (let num of items) { console.log( num ); // 1, 2, 3, 4 }
Map - map from key to value
var map = new Map(); map.set("name", "John"); map.set(23, "age"); console.log( map.has(23); ) // true console.log( map.get(23) ); // "age" console.log( map.size ); // 2
Map vs Object
Modifying a Map
var map = new Map([ ['name', 'John'], [23, 'age'] ]); console.log( map.size ); // 2 map.delete(23); console.log( map.get(23) ); // undefined map.clear(); console.log( map.size ); // 0* Note that you can pass map key-valur pairs to the constructor
Iterating over a Map
var map = new Map([ ['name', 'John'], [23, 'age'] ]); for (var value of map.values()) { ... } for (var key of map.keys()) { ... } for (var item of map.items()) { console.log('key: ' + item[0] + ', value: ' + item[1]); } for (var item of map) { // same as iterating map.items() } map.forEach(function(value, key, map) { ... });
WeakMap - similar to Map, but...
var weakMap = new WeakMap(); var key = { stuff: true }; weakMap.set(key, 123); // weakMap contains 1 item delete key; // weakMap is now empty
Typed objects are similar to struct objects in C
They provide memory-safe, structured access to contiguous data
They can expose a binary representation, making serialization/de-serialization easy
Example: represent a 2D point
const Point2D = new StructType({ x: uint32, y: uint32 });
Can access the underlying storage of the struct
let p = Point2D({x : 0, y : 0}); p.x = 5; let arrayBuffer = Point.storage(p).buffer; typeof arrayBuffer // ArrayBuffer arrayBuffer.byteLength // 8* 8 bytes becase we have two 32-bit (4-byte) values
Hierarchy of typed objects
const Corner = new StructType({ point: Point2D }); const Triangle = Corner.dim(3); let t = Triangle([{ point: { x: 0, y: 0 } }, { point: { x: 5, y: 5 } }, { point: { x: 10, y: 0 } }]); t[0].point.x = 5;
A type object and all of its sub objects share the same memory
let t = Triangle([{ point: { x: 0, y: 0 } }, { point: { x: 5, y: 5 } }, { point: { x: 10, y: 0 } }]); Triangle.storage(t).buffer.byteLength; // 24* Sub objects are stored in slots within the larger object's memory
Typed objects use copy-on-write
const Corner = new StructType({ point: Point2D }); let p = Point2D({ x: 1, y: 1 }); let c = Corner({ point: {x: 2, y: 2} }); c.point = p; // p gets copied c.point.x = 5; p.x; // 1* Normal object assignements simply reference the source object. But with structs a copy is done.
Built-in value types
Direct proxies allows you to intercept calls made to a regular object
They can wrap any Object, including Date, Function, etc.
Proxies are meant to work 'transparently'
var target = []; var handler = { get: function() {...} }; var p = Proxy(target, handler); Object.prototype.toString.call(p) // "[object Array]" Object.getPrototypeOf(p) // Array.prototype typeof p // "object" Array.isArray(p) // true p[0] // triggers handler.get(target, "0", p) p === target // falseProxy handler is an object which can override one or more methods prototype still points to original object one typeof and instanceof calls automatically use original object The point is that clients which use a proxy can't easily tell that it's a proxy
Proxy handler can choose what to intercept
getOwnPropertyDescriptor getOwnPropertyNames getPrototypeOf defineProperty deleteProperty freeze seal preventExtensions isFrozen isExtensible
isSealed has hasOwn get set enumerate keys apply construct* If no intercept present then target object's equivalent is invoked
What template strings allow
Basic substitution
var name = "Tom", msg = `Hello, ${name}!`; console.log(msg); // "Hello, Tom!"* Notice the use of backquotes
Expressions
var total = 30, msg = `The total is ${total * 2} units`; console.log(msg); // "The total is 60 units"
Multi-line
var total = 30, var msg = `The total is ${total * 2} units`;
Functions
// Gets called with: // ['Total is ', ' units'] // 60 var myFunc = function(literals) { var str = '', i = 0; while (i < literals.length) { str += literals[i++]; if (i < arguments.length) { str += '[' + arguments[i] + ']'; } } return str; }; var total = 30; console.log( myFunc`Total is ${total * 2} units` ); // Total is [60] unitsThe substitution values are already calculated by the time myFunc is called Can use this for HTML escaping, etc
with a few exceptions (full details)
* Mozilla Developer docs explain all of theseNew Math functions
log10, log2, log1p, expm1, cosh, sinh, tanh, acosh, asinh, atanh, hypot, trunc, sign
* Mozilla Developer docs explain all of theseNumber
.isFinite()
.isNaN() - better than isNaN()
.isInteger()
.EPSILON - smallest values such that 1 + Number.EPSILON > 1
String
.repeat(n) - copy current string n times
.startsWith(str)
.endsWith(str)
.contains(str)
.toArray() - same as .split('')
class Laptop { constructor() { this.brand = 'asus'; } on() { ... } off() { ... } }
class SmashedLaptop extend Laptop { constructor() { super(); this.pieces = []; } }
• import the default export of a module import $ from "jquery";
• binding an external module to a variable module crypto from "crypto";
• binding a module's exports to variables import { encrypt, decrypt } from "crypto";
• binding & renaming one of a module's exports import { encrypt as enc } from "crypto";
• re-exporting another module's exports export * from "crypto";
• re-exporting specified exports from another module export { foo, bar } from "crypto";
module "chocolate" { export let cocoa = 75; }
In another file:
import { cocoa } from "chocolate"; // or import { cocoa as c} from "chocolate";
module "foo" { export default function() {console.log("Hi")} }
import foo from "foo"; // no brackets foo(); // Hi
See http://kangax.github.io/es5-compat-table/es6/ for more