A brief look at the new features coming in Javascript ES6:
- Scope and control
- Iterators and Generators
- Collections
- Typed objects
- Direct proxies
- Template strings
- API improvements
- Modularity
Speakers
Grégoire Charvet (geekingfrog.com)
Full time node.js developper
Passionate about the web
Working on the web for almost 2 years now
Ramesh Nair (hiddentao.com)
Full time Javascript/Node developper
Also worked with PHP, Python, Java, C++
Loves optimizing for performance
The birth
Created in 10 days in 1995 (by Brendan Eich) at Netscape
Brought to ECMA a year later to standardize it
javaScript has nothing to do with java
Early history
ECMAScript 2 in 98, 3 in 99
War with Microsoft -> ES4 has never been adopted
In 2005, Microsoft introduced ajax
In 2009, all parties agreed to move forward with ES5 +
harmony process
Now
Javascript most well known implementation of ECMAScript
(with ActionScript)
Javascript is the assembly of the web
Confusing version number, JS 1.8 correspond to ES6
ES6
ES6 work started in 2011
As of now (Feb 2014), ES6 is not yet adopted (but it's almost
there)
Major milestone
Not 'production ready' yet
What we will cover today
Support
Scope and control
Iterators and Generators
Collections
Typed objects
Direct proxies
Template strings
API improvements
Modularity
Undefined and null
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
Arity
Number of parameters without default value
(function(a){}).length // 1
(function(a=10){}).length // 0
(function(a=10, b){}).length // 1
Example
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
Sweet syntax
var f = function(a, b, c, d, e, f) {};
var a = [1, 2];
f(-1, ...a, 3, ...[-3, -4]);
Apply for new
With es5, one cannot use apply with new .
var Constructor = function() {
// do some stuff
}
var c = new Constructor.apply({}, []); //invalid
But now:
var dataFields = readDateFields(database);
var d = new Date(...dateFields);
Better push
To push multiple elements:
var a = [];
var toPush = [1, 2, 3];
a.push.apply(a, toPush);
And now:
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.
Arrays are iterable:
var a = [1,2,3],
i = a.iterator();
console.log(i.next());
console.log(i.next());
console.log(i.next());
console.log(i.next());
//
//
//
//
{done:
{done:
{done:
{done:
false, value: 1}
false, value: 2}
false, value: 3}
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' ]
We can make any object iterable:
function ClassA() {
this.elements = [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!
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...');
}
Yield 1...
{ done: false, value: 'hello'
}
Yield 2...
{ done: false, value: 'world'
}
No more yields...
{ done: true, value: undefined
}
var hw = helloWorld();
console.log( hw.next() );
console.log( hw.next('world') );
console.log( hw.next() );
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() );
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...
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();
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();
}
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);
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.
Set - no duplicates allowed
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);
// 5
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
Maps can have non-string keys
Maps don't have prototype leakage issues, i.e. no need to
use hasOwnProperty()
But
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
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...
Only allows Object keys
Only holds a reference to the Object used as a key, so it
doesn't prevent garbage collection
Do no provide a size
Cannot be iterated over
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
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
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
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 // false
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
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] units
Call the parent
class SmashedLaptop extend Laptop {
constructor() {
super();
this.pieces = [];
}
}
Key points
constructor replace the function definition in es5
No access to the prototype of the class
Methods are defined the same way as objects
Can call the parent with super (and perform initialization
within the constructor)
Modules
• 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";
Modules
• 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";
Why ?
No need for the global object anymore
Works well with existing modules system (AMD,
CommonJS and node)
Simplicity and usability
Compatibility with browser and non-browser environments
Easy asynchronous external loading
Exporting and importing
module "chocolate" {
export let cocoa = 75;
}
In another file:
import { cocoa } from "chocolate";
// or
import { cocoa as c} from "chocolate";
Default export
module "foo" {
export default function() {console.log("Hi")}
}
import foo from "foo"; // no brackets
foo(); // Hi
Internals
Top-level variables stay inside the module
export make variables visible to the other modules
Can be read (get)
Cannot be changed (set)
Cannot be dynamically changed at runtime
Modules are recursively instantiated before evaluation
Modules' body is run after all dependencies are instantiated