ES6 – Next Generation Javascript – Vertical Slides



ES6 – Next Generation Javascript – Vertical Slides

0 4


es6-slides

Slides on the new features in Javascript ES6

On Github hiddentao / es6-slides

ES6

Next Generation Javascript

By Ramesh Nair and Grégoire Charvet

https://github.com/hiddentao/es6-slides

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

Rapid history of javascript

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
* We will only over the most awesome new features

Support

24 30 ✗

For chrome, need to enable the experimental js features

full table

Node.js support

Node.js: get the latest 0.11 and add the flag --harmony

Support is the same as chrome (V8)

Safari support

Almost the same as IE (so quasi inexistant)

Scoping

24 30 11

Block scoping

Finally !

Before

for(var i=10; i>0 ; i--) {
  // do stuff with i
}
console.log(i); // 0

let

for(let i=10; i>10; i--) {
}
console.log(i); // `i is not defined`

Works with if too

var log = function(msg) {};
if(someCond) {
  let log = Math.log;
  // do some stuff
}
log("I'm done here");

Easier closures

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

Easy fix

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

let('s) recap

  • works like var
  • scope is defined by the current block ({ })

const

Like let, but define read-only constant declarations.

'use strict';
const i = 10;
i = 5; // throw error

Destructuration

24 ✗ ✗

With array

// Assignment
var [day, month, year] = [19, 02, 2014];

// Swap two values.
var x=3, y=4;
[x, y] = [y, x];

Array with functions

var now = function() { return [19, 02, 2014]; }
var [day, month] = now();
var [, , year] = now();

With objects

var now = function() { return {
  d: 19,
  m: 2,
  y: 2014
}}

var {d: day, m: month} = now();
// day: 19
// month: 2

As argument of a function

recipes = [
  { name: 'burger', calorie: 215 },
  { name: 'pizza', calorie: 266 } ];

recipes.forEach(function
  ({ name: name, calorie: calorie }) {
    console.log(name, calorie);
  }
);

Default function parameters

24 30 ✗

Ability to define default value for functions paramaters.

No more:

function(a) {
  if(!a) { a = 10; }
  // do stuff with a
}

Now

function(a=10) {
  // do stuff with a
}

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

Rest parameters

24 ✗ ✗

Better than arguments

function(name) {
  console.log(name);
  arguments[0] = 'ME !';
  console.log(name); // ME !
  Array.isArray(arguments); // false
}

Now

function(...args) {
  Array.isArray(args); // true
  // do some stuff
}

function(name, ...more) {

}

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

Restrictions

Rest parameters can only be the last parameter

// incorrect
function(...args, callback) {
}

Details

Rest parameter is always an array

function f(name, ...more) {
  Array.isArray(more); // always true
  return more.length;
}
f(); // returns 0

Arity

Does not include the rest parameter

(function(a) {}).length // 1
(function(a, ...rest) {}).length // 1

Spread

24 (with array) 27-28 (with functions) ✗ ✗

Expand expression where multiple arguments or multiple element are needed

More powerful array manipulation

Usecase: create new array with an existing one inside it:

var from = [1, 2];
// wants: [0, 1, 2, 3] ie [0, from, 3]

With es5

a.unshift(0);
a.push(3);
// and splice is here also

With es6

var total = [0, ...from, 3];

Converting any array-like

Array like ???

  • Object with a length property
  • Can access elements with []
var fake = {
  0: 'I am',
  1: 'not',
  2: 'an aray',
  length: 3
};

Array like in the wild

  • Function's arguments
  • All nodeList from the DOM

Before:

var nodes = document.querySelectorAll('p');
var nodes = [].slice.call(nodes);
And now:
nodes = [...nodes];

Array conversion

Better way:

Array.from(document.querySelectorAll('p'));

Out of the scope of the talk.

Spread with functions

A better apply

var f = function(one, two, three) {}
var a = [1, 2, 3];
f.apply(null, a);

Apply ?

  • Function.prototype.apply
  • fun.apply(thisArg, [argsArray])

Apply example

function f() {
  for(let i=0; i<arguments.length; ++i)
    console.log(arguments[i]);
}

f.apply(this, ['one', 2, 'foo']);
// one
// 2
// foo

With es6's spread

var f = function(one, two, three) {}
var a = [1, 2, 3];
f(...a);

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

Next...

Iterators

24 ✗ ✗

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 supported

Arrays 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
}

Generators

27 30 ✗

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!

Browser: Only Firefox Nightly 29 for now. Firefox stable supports older syntax and semantics. Node: Generator functions and yield supported

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.

* co is one of the key building blocks for Koa, the successor to Express.

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/

Next...

Collections

24 30 11

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
Note 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

  • 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
* 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...

  • 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

Next...

Typed objects

✗ ✗ ✗

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

  • uint8, uint8Clamped
  • uint16
  • uint32
  • int8
  • int16
  • int32
  • float32
  • float64
  • boolean
  • string

Next...

Direct proxies

24 ✗ ✗

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 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

Next...

Template strings

✗ ✗ ✗

What template strings allow

  • Multi-line strings
  • String formatting - think printf from C
  • HTML escaping
  • Localization/internationalization
* Bullet points taken from: http://www.nczonline.net/blog/2012/08/01/a-critical-review-of-ecmascript-6-quasi-literals/

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] units
The substitution values are already calculated by the time myFunc is called Can use this for HTML escaping, etc

Next...

API improvements

24 ✗ ✗

with a few exceptions (full details)

* Mozilla Developer docs explain all of these

New Math functions

log10, log2, log1p, expm1, cosh, sinh, tanh, acosh, asinh, atanh, hypot, trunc, sign

* Mozilla Developer docs explain all of these

Number

  • .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('')

Next...

Modularity

✗ ✗ ✗

Classes

In es5

  • Classes doesn't exist natively
  • Prototype based inheritance
  • Framework and libraries implement their own class system

New keyword

class Laptop {
  constructor() {
    this.brand = 'asus';
  }

  on() { ... }
  off() { ... }
}

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

That's all for today!

See http://kangax.github.io/es5-compat-table/es6/ for more

Useful links