ECMASCRIPT 6

The ES.next version of JavaScript

Why should I care... now?

  • JavaScript is currently being used in contexts it was not design for
  • Important dates:
    • Aug 2014: Feature frozen
    • Dec 2014: ECMA approval
    • Mar 2015: Publication starts
    • Mar 2015: Formal publication
  • ES6 is coming... :O

Usefull references to Try Es6

  • 6to5.org
  • ES6Fiddle.net
  • CodePen.io:
    • Traceur option in Javascript options
  • Node.js:
    • --harmony option (only in current dev branch)
  • io.js:
    • Since it uses the latest versions of V8
    • --harmony option for staged (almost complete) features
    • --harmony-feature option:
      • --harmony-generators
      • --harmony-arrow-functions

let is the new var

(function scope() {
  if (true) {
    var tmp1 = 1;
    let tmp2 = 2;
  }
  console.log(tmp1); // '1' -> http://jslinterrors.com/a-used-out-of-scope
  console.log(tmp2); // ReferenceError: tmp2 is not defined
})();

console.log(tmp1); // ReferenceError: tmp1 is not defined
let [a, b] = [1, 2];
console.log(a + ' - ' + b); // 1 - 2

let [c, d, ...rest] = [1, 2, 3, 4, 5, 6];
console.log(c + ' - ' + d + ' - ' + rest); // 1 - 2 - 3, 4, 5

let [e,,f] = [1, 2, 3];
console.log(e + ' - ' + f); // 1 - 3

[b, a] = [a, b]; // Swapping values
console.log(a); // 2 console.log(b); // 1
  • Block scope:
  • Array destructuring:

let is the new var

let obj = {first: 'Jander', last: 'Klander'};
console.log(JSON.stringify(obj)); // '{"first":"Jander","last":"Klander"}'

let {first: g, last: f} = obj; // Destructuring the object
console.log(g + ' ' + f); // 'Jander Klander'

let {first, last} = obj; // Shorthand: {x, y} <-> {x: x, y: y}
console.log(first + ' ' + last); // 'Jander Klander'
function multipleReturn(value1, value2) {
  return {arg1: value1, arg2: value2};
}

let {arg1: first, arg2: second} = multipleReturn(1, 2);
console.log(first); // 1
console.log(second); // 2

let {arg2, arg1} = multipleReturn(3, 4); // Shorthand form. No ordering
console.log(arg1); // 3
console.log(arg2); // 4
  • Object destructuring:
  • Multiple return values:

let: TO HOIST OR NOT TO HOIST

function print() {
  var text = 'Just some text';
  console.log(text);
}
print();
// Just some text
function print() {
    console.log(text);
    var text = 'Just some text';
}
print();
// undefined
function print() {
    console.log(text);
    let text = 'Just some text';
}
print();
// ReferenceError: text is not defined

let is the new var: EXERCISE 1

// Create a loop which sets 10 timeouts at random times and print by the
//  console the position at which each one was set when run.
// SOLUTION:
for (let i = 1; i <= 10; i++) { // What happens if |var i = 0| ?
    setTimeout(function() {
      console.log(i);
    }, Math.random() * 1000);
}
// 4
// 2
// 6
// 3
// 5
// 7
// 8
// 10
// 1
// 9

let is the new var: EXERCISE 2

// Create a block scope in ECMAScript 5 and compare it to how it can be done
//  now in ECMAScript 6.
// SOLUTION: In ECMAScript 5
(function blockScope() {
  var a = 1;
  console.log(a);
})();

console.log(a);
// 1
// ReferenceError: a is not defined
// SOLUTION: In ECMAScript 6
{
  let a = 1;
  console.log(a);
}

console.log(a);
// 1
// ReferenceError: a is not defined

constants support

const a = 1;
a = 2; // TypeError: a is read-only

const obj = {
  property: 'value'
};
obj = {}; // TypeError: obj is read-only
obj.property = 'updated value';
obj.anotherProperty = 'another value';
console.log(JSON.stringify(obj));
// {"property":"updated value","anotherProperty":"another value"}
/* Block scope just as let */
(function scope() {
  if (true) {
    var tmp1 = 1;
    const tmp2 = 2;
    tmp2 = 3; // TypeError: tmp2 is read-only
  }
  tmp2 = 4; // ReferenceError: tmp2 is not defined
  console.log(tmp1); // '1' -> http://jslinterrors.com/a-used-out-of-scope
})();
// Function calls:
function myFunc(...iterableObject) {
  console.log(iterableObject);
};

myFunc(1, 2, 3); // Array [1, 2, 3]

// Array literals:
var iterableObject = [3, 4, 5, 6];
console.log([1, 2, ...iterableObject, 7, 8, 9]);
// Array [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

// Array destructuring:
var first, rest;
[first, ...rest] = [1, 2, 3, 4, 5]; // Only at the end.
console.log(first); // 1
console.log(rest); // Array [ 2, 3, 4, 5 ]
  • Allows an expression to be expanded in places where multiple arguments (function calls) or multiple elments (array literals) are expected:

Function arguments

var total = 0;

// It can be an expression, and can have side effects :)
function sum(a=0, b=total*2, c=++a) {
  return total = a+b+c;
}

console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2)); // 6 -> WAAAT???
console.log(sum(1)); // 16
console.log(sum()); // 34
function func(a, b, ...rest) {
  return a + ' - ' + b + (rest.length ? ' - ' + rest : '');
}

console.log(func(1, 2)); // 1 - 2
console.log(func(1, 2, 3, 4)); // 1 - 2 - 3,4
  • Default values:
  • Rest arguments:

Function arguments

function sum(a, {v1: b, v2: c}) {
 return a + b + c; 
}

console.log(sum(1, {v1: 2, v2: 3})); // 6
  • Named arguments:

Function arguments: EXERCISE

// Create a function which adds (pushes) the elements of an array at the end of
//  another array and returns the new array using the spread operator:
console.log(push([1, 2], [3, 4, 5])); // [1, 2, 3, 4, 5]
// SOLUTION:
function push(arr1, arr2) {
  arr1.push(...arr2);
  return arr1;
}

var arr1 = [1, 2];
var arr2 = [3, 4, 5];
console.log(push(arr1, arr2)); // Array [ 1, 2, 3, 4, 5 ]
// SOLUTION in ECMAScript 5:
function push(arr1, arr2) {
  Array.prototype.push.apply(arr1, arr2);
  return arr1;
}

var arr1 = [1, 2];
var arr2 = [3, 4, 5];
console.log(push(arr1, arr2)); // Array [ 1, 2, 3, 4, 5 ]

Arrow functions

// Longer version: (arg1, arg2, ...) => { statement1; statement2; ... }
let double1 = [1, 2, 3].map((x) => { return x + x; });
console.log(double1); // Array [2,4,6]

// Shorter version: arg => expression
let double2 = [1, 2, 3].map(x => x + x);
console.log(double2); // Array [2,4,6]
  • Special type of function:
    • Bound (lexical) this
    • It cannot be used as a constructor
    • No arguments variable
// Lexical this:
//   (x) => { return x + this.y; } <=> function(x) { return x + this.y; }.bind(this)
var myObj = {
  func: function() {
    let double2 = [1, 2, 3].map((x) => { console.log(this === myObj); return x + x; });
    console.log(this === myObj);
    console.log(double2);
  }
};
myObj.func(); // true // true // true // true // [2,4,6]
// Lexical this:
//   (x) => { return x + this.y; } <=> function(x) { return x + this.y; }.bind(this)
var myObj2 = {
  func: function() {
    let double2 = [1, 2, 3].map(function(x) { console.log(this === myObj2); return x + x; });
    console.log(this === myObj2);
    console.log(double2);
  }
};
myObj2.func(); // false // false // false // true // Array [2,4,6]

Arrow functions: EXERCISE

// Use an arrow function to calculate the sum of all the elements in an array.
console.log(sum([1, 2, 3])); // 6
// SOLUTION:
function sum(arr) {
    var val = 0;
    arr.forEach(entry => val += entry);
    return val;
}

console.log(sum([1, 2, 3])); // 6

Promises: introduction

  • Managing asynchrony in ECMAScript 5:
    1. Events:
      • Implement the EventTarget interface (front-end)
      • Inherit from the EventEmitter object (Node.js)
    2. Callbacks:
      • Continuation-passing style
      • Pyramid of doom or callback hell
      • Complex error handling
  • Javascript de-facto standard for promises:

Promises: basic concepts

  • Promise:
    • Eventual result of an asynchronous operation
    • Registration of callbacks:
      • Result is available (resolve)
      • Error (reject)
    • Possible states:
      • Pending
      • Settled (cached value or reason):
        • Fulfilled
        • Rejected

Promises: production & consumption

  • Promise production:
var promise = new Promise(function executor(resolve, reject) {
    ...
    if (...) { resolve(value); } else { reject(reason); }
});
  • Promise consumption:
// Since the settling is cached. If the promise is already
//  settled once the 'then' is invoked, the corresponding
//  callback will be called as the next task in the queue.
promise.then(
    // Returns a new promise fulfilled with 'onFulfilled' or 'onRejected'
    //  result or rejected if 'fulfilled' or 'rejected' throws exception.
    function onFulfilled(value) { /* Promise resolved with value */ },
    function onRejected(reason) { /* Promise rejected because reason */ }
);
promise.catch(
    function(reason) { /* Promise or promise chain rejected because reason */ }
);
p1.then(function() { return p2; }) // p2 inserted here before next 'then'
  .then(onFulfilled, onRejected);

Promises: COMPOSITION

  • Promise.all():
Promise.all([promise1, promise2, value1, thenable1, ..., promiseN])
    // Returns a promise.
    // values and thenables are converted into promises.
    .then(function(arrayOfValues) { /* All are fulfilled with value */ })
    .catch(function() { /* First rejection from promises */ });
  • Promise.race():
Promise.race([promise1, promise2, value1, thenable1, ..., promiseN])
    // Returns a promise
    // values and thenables are converted into promises
    .then(function(value) { /* The first promise is fulfilled with value */ })
    .catch(function(reason) { /* First rejection from promises */ });

Promises: examples

  • Examples:
// Delaying an activity.
function delay(ms) {
    return new Promise(function(resolve) {
        setTimeout(resolve, ms);
    });
}
delay(3000).then(
    function() { console.log('After 3 seconds'); }
);
// Timing out a promise.
function timeout(ms, promise) {
    return new Promise(function(resolve, reject) {
        promise.then(resolve, reject);
        setTimeout(function() {
            reject(new Error('Timeout'));
        }, ms);
    });
}
timeout(3000, somePromise)
.then(
    function(value) { /* Promise fulfilled with value */ })
.catch(
    function(reason) { /* Promise rejected or timeout */ });
  • Object that provides a next() function which returns the next item in the sequence:
    • {done: true|false, value: nxt_value}
    • It can optionally raise a StopIteration exception when the sequence is exhausted
function ObjectIterator(entries) {
  this.entries = entries;
  this.keys = Object.keys(this.entries);
  this.current = -1;
}

ObjectIterator.prototype.next = function next() {
  this.current++;
  if (this.current < this.keys.length) {
    let key = this.keys[this.current],
        value = this.entries[key];
    return {done: false, value: [key, value]};
  } else {
    return {done: true};
  }
};

let pairs = {'first': 1, 'second': 2, 'third': 3};

let iter = new ObjectIterator(pairs);

console.log(iter.next()); // {done: false, value: ['first', 1]}
console.log(iter.next()); // {done: false, value: ['second', 2]}
console.log(iter.next()); // {done: false, value: ['third', 3]}
console.log(iter.next()); // {done: true}
  • Object iterator:
function ArrayIterator(array) {
  this.array = array;
  this.current = -1;
}

ArrayIterator.prototype.next = function next() {
  this.current++;
  if (this.current < this.array.length) {
    let key = this.current,
        value = this.array[key];
    return {done: false, value: [key, value]};
  } else {
    return {done: true};
  }
};

let elements = ['first', 'second', 'third'];
let iter = new ArrayIterator(elements);

console.log(iter.next()); // {done: false, value: [0, 'first']}
console.log(iter.next()); // {done: false, value: [1, 'second']}
console.log(iter.next()); // {done: false, value: [2, 'first']}
console.log(iter.next()); // {done: true}

// New array.entries() returns an iterable
  • Array iterator:
let myArray = ['first', 'second', 'third'];

for (let entry of myArray) {
  console.log(entry);
  // first'
  // 'second'
  // 'third'
}
let myArray = ['first', 'second', 'third'];

let entries = myArray.entries();

console.log(entries.next().value); // {done: false, value: [0, 'first']}
console.log(entries.next().value); // {done: false, value: [1, 'second']}
console.log(entries.next().value); // {done: false, value: [2, 'third']}
console.log(entries.next().value); // {done: true, value: undefined}
  • Special kind of function that can be suspended and resumed via an iterator
  • Cooperative vs Imperative
  • function* name() <=> function *name()
  • yield operator:
    • "resumable return"
setTimeout(function() {
  console.log('Am I late?');
});

for (let i = 0; i < 100; i++) {
  console.log('And ' + i + '...'); 
}
// And 0...
// And 1...
// And 2...
// And 3...
// And 4...
// And 5...
// ...
// And 95...
// And 96...
// And 97...
// And 98...
// And 99...
// Am I late?
  • Run-to-completion semantics:

Generators: Flow

Generators: Iterators

function *gen() {
  yield 1;
  yield 2;
  yield 3;
  // return 4; // Not considered if of operator
}
let iterator = gen();

let entry,
    done = false;
while (!done) {
  entry = iterator.next();
  done = entry.done;
  console.log(entry);
  // {"value":1,"done":false} // {"value":2,"done":false} // {"value":3,"done":false}
  // {"done":true} | {"value":4,"done":true} 
}
console.log(iterator.next()); // {"done":true} | {"done":true}
iterator = gen();

// No way to pass values to next()
for (let entry of iterator) {
  console.log(entry); // 1 // 2 // 3
}

Generators: yield operator

// The generator function can take arguments.
function *foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

var it = foo(5);

// Not sending anything into next() here.
console.log(it.next()); // {"value":6,"done":false}
console.log(it.next(12)); // {"value":8,"done":false}
console.log(it.next(13)); // {"value":42,"done":true}
function foo2(x) { console.log("x: " + x); }

function *bar(val) {
  var x = yield (val + 1);
  foo2(yield (x + 2));
}

var it = bar(5);
console.log(it.next()); // {"value":6,"done":false}
console.log(it.next(1)); // {"value":3,"done":false}
console.log(it.next(2));
// x: 2
// {"done":true}

Generators: Error handling

function *foo() {
  try {
    var x = yield 3;
    console.log('x: ' + x);
  } catch (err) {
    console.log('Error: ' + err );
  }
}

var it = foo();
console.log(it.next()); // {"value":3,"done":false}
it.throw('Oops!'); // Error: Oops!
function *foo2() {
  var x = yield 3;
  return x.nonExistent();
}

var it2 = foo2();
try {
  console.log(it2.next()); // {"value":3,"done":false}
  // Makes the generator throw TypeError: Cannot read property 'nonExistent'
  //  of undefined
  console.log(it2.next());
} catch (exception) {
  console.log('EXCEPTION!!!: ' + exception);
  // EXCEPTION!!!: TypeError: Cannot read property 'nonExistent' of undefined
}

Generators: Delegation

function *foo(val) {
  yield val; // Sent values pass through the delegation transparently
  yield ++val; // Sent values pass through the delegation transparently
  return ++val; // Returned value to the generator which delegates
}

function *bar() {
  yield 1;
  yield *foo(3); // Delegates iteration control to foo()
  yield 6;
}

for (var v of bar()) { // Possibility to pass values if next()
  console.log(v);
}
// 1 -> by bar
// 3 -> by foo
// 4 -> by foo
// 6 -> by bar

// Error handling as expected: just flows in and out generators

Generators: EXERCISE

// Print by the console the Fibonacci series (each element in the series is the
//  sum of the previous 2 elements) until certain value using generators and
//  iterators.
fibonacciUntil(1000); // 1 // 2 // 3 // ... // 610 // 987
// SOLUTION:
function *fibonacci() {
  let prev, curr;
  [prev, curr] = [0, 1];
  while (true) {
    [prev, curr] = [curr, prev + curr];
    yield curr;
  }
}

function printFibonacciUntil(max) {
  for (let value of fibonacci()) {
    if (value > max) {
      break;
    }
    console.log(value);
  }
}

printFibonacciUntil(1000);
// SOLUTION:
function *fibonacci() {
  let curr = 1,
      prev = 0;
  while (true) {
    curr = yield curr + prev;
    prev = curr - prev;
  }
}

function fibonacciUntil(max) {
  let iterator = fibonacci(),
      curr = iterator.next().value;
  do {
    console.log(curr);
    curr = iterator.next(curr).value;
  } while (curr < max);
}

fibonacciUntil(1000);

Generators: Asynchronity

function getData(url, cb) {
  // Fetch data from URL
  cb(data);
}

function getURL(data) {
  // Process data to get URL
  return url;
}

getData('http://example.com',
  function(data) {
    let url2 = getURL(data);
    getData(url2, function(data2) {
      let url3 = getURL(data2);
      getData(url3, function(data3) {
        doSomething(data3);
      });
    });
  }
);
function request(url) {
  getData(url, function(data) {
    it.next(data);
  });
}

function *gen(url1) {
  let data1 = yield request(url1);
  let url2 = getURL(data1);
  let data2 = yield request(url2);
  let url3 = getURL(data2);
  let data3 = yield request(url3);
  doSomething(data3);
}

var it = gen('http://example.com');
it.next();
var cache = {};

function request(url) {
  if (cache[url]) {
    setTimeout(function() {
      it.next(cache[url]);
    });
  } else {
    getData(url, function(data) {
      cache[url] = data;
      it.next(data);
    });
  }
}

function *gen(url1) {
  let data1 = yield request(url1);
  let url2 = getURL(data1);
  let data2 = yield request(url2);
  let url3 = getURL(data2);
  let data3 = yield request(url3);
  doSomething(data3);
}

var it = gen('http://example.com');
it.next();

Generators: Promises

function request(url) {
  return new Promise(function (resolve, reject) {
    getData(url, resolve);
  });
}

function runGenerator(gen) {
  var it = gen(), ret;
  (function iterate(val) {
    let ret = it.next(val);
    if (!ret.done) {
      if ('this' in ret.value) {
        ret.value.then(iterate);
      } else {
        setTimeout(function() {
          iterate(ret.value);
        });
      }
    }
  })();
}

runGenerator(function *gen() {
  let data1 = yield request('http://example.com');
  let url2 = getURL(data1);
  let data2 = yield request(url2);
  let url3 = getURL(data2);
  let data3 = yield request(url3);
  doSomething(data3);
});
function request(url) {
  return new Promise(function (resolve, reject) {
    getData(url, resolve);
  });
}

function runGenerator(gen) {
  var it = gen(), ret;
  (function iterate(val) {
    let ret = it.next(val);
    if (!ret.done) {
      if ('then' in ret.value) {
        ret.value.then(iterate);
      } else {
        setTimeout(function() {
          iterate(ret.value);
        });
      }
    }
  })();
}

runGenerator(function *gen() {
  let results = yield Promise.all([
    request('http://example.com/path1'),
    request('http://example.com/path2'),
    request('http://example.com/path3'),
  ]);
  let data = yield request('http://example.com/' + results.join('/'));
  doSomething(data);
});

Generators: ECMAScript7

async function main() {
  var result1 = await request('http://some.url.1');
  var data = JSON.parse(result1);

  var result2 = await request('http://some.url.2?id=' + data.id);
  var resp = JSON.parse(result2);
  console.log('The value you asked for: ' + resp.value);
}

main();

PROXIES: INTRODUCTION

  • Meta-programming feature
  • Reflective meta-programming:
    • A program processes itself
    • Types:
      • Introspection:
        • Read-only access to the structure
        • Supported in Javascript
      • Self-modification:
        • Supported in Javascript
      • Intercession:
        • Redefining the semantics of some operations
        • Proxies in Javascript where created for this

PROXIES: TARGET & HANDLER

  • new Proxy object
  • Proxy properties:
    • target
    • handler -> placeholder object of traps (if no trap, forwarded to target)
handler.getPrototypeOf() // A trap for Object.getPrototypeOf.
handler.setPrototypeOf() // A trap for Object.setPrototypeOf.
handler.isExtensible() // A trap for Object.isExtensible.
handler.preventExtensions() // A trap for Object.preventExtensions.
handler.getOwnPropertyDescriptor() // A trap for Object.getOwnPropertyDescriptor.
handler.defineProperty() // A trap for Object.defineProperty.
handler.has() // A trap for the in operator.
handler.get() // A trap for getting property values.
handler.set() // A trap for setting property values.
handler.deleteProperty() // A trap for the delete operator.
handler.enumerate() // A trap for for...in statements.
handler.ownKeys() // A trap for Object.getOwnPropertyNames.
handler.apply() // A trap for a function call.
handler.construct() // A trap for the new operator. 

PROXIES: OBJECT EXAMPLE

let target = {};

let handler = {
  get: function(target, propKey, receiver) {
    console.log('Getting ' + propKey);
    return target[propKey]; // Reflect.get(target, propKey);
  },
  ownKeys: function(target) {
    console.log('Getting own keys...');
    return ['key1', 'key2']; // Reflect.ownKeys(target);
  }
};

let proxy = new Proxy(target, handler);

proxy.foo = 'bar'; // Getting foo
console.log(proxy.foo); // bar
console.log(Object.getOwnPropertyNames(proxy));
// Getting own keys...
// Array [ "key1", "key2" ]

PROXIES: FUNCTION EXAMPLE

let Person = function(name) { this.name = name; };

let personHandler = {
    apply(target, thisArg, argumentList) {
        console.log('Function called with ' + argumentList);
        return target.bind(this, ...argumentList);
    },
    construct(target, argumentList) {
        console.log('Instantiating Person' + ' with ' + argumentList);
        return new target(...argumentList);
    }
};

let PersonProxy = new Proxy(Person, personHandler);

PersonProxy('Paco'); // Function called with Paco
var person = new PersonProxy('Pepe'); // Instantiating Person with Pepe
console.log(person.name); // Pepe

PROXIES: REVOCABLE PROXIES

let target = {};

let handler = {
    get(target, propKey, receiver) {
        console.log('Getting ' + propKey);
        return target[propKey]; // Reflect.get(target, propKey);
    },
    ownKeys(target) {
        console.log('Getting own keys...');
        return ['key1', 'key2']; // Reflect.ownKeys(target);
    }
};

let {proxy, revoke} = Proxy.revocable(target, handler);

proxy.foo = 'bar'; // Getting foo
console.log(proxy.foo); // bar

revoke();

console.log(Object.getOwnPropertyNames(proxy)); // TypeError: illegal operation
// attempted on a revoked proxy

PROXIES: USE CASES

  • Data binding:
    • Updating the view when the model changes (MVC)
  • Local placeholders that forward method invocations to remote objects
  • Data access objects for databases: reading and writing to the object reads and writes to the database
  • Profiling:
    • Intercept method invocations to track how much time is spent in each method
  • Type checking:
    • handler.set(target, property, value, receiver)

PROXIES: EXERCISE

// Create a function called 'createArray' which allows the creation of Arrays
//  supporting negative indexes using a Proxy object, this is:
let arr = createArray('a', 'b', 'c');
console.log(arr[-1]); // c
// SOLUTION:
function XArray() {
  return new Proxy([], {
    get: (target, prop) => {
      var index = +prop;
      var isNumber = !isNaN(index);
      if (isNumber) {
       return target[index < 0 ? target.length + index : prop];
      }
      return target[prop];
    }   
  });
}

var xarray = new XArray();
xarray.push(1, 2, 3, 4);
console.log(xarray[-2]); // 3
  • Different kind of function call
  • Mechanism for defining templates
  • String literals allowing embedded expressions
  • Syntactic construct which facilitates the implementation of DSL
  • Simple syntax for creating data
  • Strings enclosed by back-ticks (`...`)
/* Multiline support */
console.log(`This is the first line
this is the second line`);
/* Tagged template strings */
function templateHandler(literals, ...values) {
    var temp = '';
    for (var i = 0; i < values.length; i++) {
        temp = temp + literals[i] + values[i];
    }
    return temp + literals[values.length];
}

let firstName = 'Jander', lastName = 'Klander';
// ${ can be any expression }
console.log(templateHandler`Hi ${firstName} ${lastName}!`); // String.raw`...`
// Hi Jander Klander!
console.log(templateHandler(['Hi ', ' ', '!'], firstName, lastName));
// Hi Jander Klander!
/* Expression interpolation */
const MAX = 10;
let value = Math.random() * 20;
if (value <= MAX) {
  console.log(`${value} is less than or equal to ${MAX}`);
} else {
  console.log(`${value} is greater than ${MAX}`);
}

Template strings: EXERCISE

// Create a function called 'names2Table' which uses string templates to transform:
[
  { first: 'Jander', last: 'Klander' },
  { first: 'Chiquito', last: 'de la Calzada' }
]
// into:
<table>
  <tr>
    <td> Jander </td> <td> Klander </td>
  </tr>
  <tr>
    <td> Chiquito </td> <td> de la Calzada </td>
  </tr>
</table>
// SOLUTION:
var names2Table = names => `
<table> ${names.map(name => `
  <tr>
    <td> ${name.first} </td> <td> ${name.last} </td>
  </tr>
`)}</table>`.replace(',', '');

console.log(names2Table([
  {first: 'Jander', last: 'Klander'},
  {first: 'Chiquito', last: 'de la Calzada'}]
));
  • New primitive type in Javascript
  • Tokens that serve as unique and immutable identifiers
// Factory function 'Symbol' accepting an optional description.
var symbol1 = Symbol('description');
var symbol2 = Symbol('description');

console.log(symbol1.toString()); // __$187584550$10$__
console.log(symbol2.toString()); // __$163428317$11$__

// Symbols are unique.
console.log(symbol1 === symbol2); // false
// Symbols can be used as property names.
let obj = {};
let property = Symbol();
obj[property] = 'value';
obj['anotherProperty'] = 'another value';

console.log(obj[property]); // value

console.log(Object.getOwnPropertyNames(obj)); // Ignores symbol-valued keys
console.log(Object.getOwnPropertySymbols(obj)); // Ignores string-valued keys

SYMBOLS: WELL-KNOWN SYMBOLS

  • Built-in symbols which represent internal language behaviours
// A method determining if a constructor object recognises an object as its instance.
Symbol.hasInstance
// A Boolean value indicating if an object should be flattened to its array elements.
//  Used by Array.prototype.concat().
Symbol.isConcatSpreadable
// A Boolean value indicating if an object may be used as a regular expression.
Symbol.isRegExp
// A method returning the default iterator for an object. Used by for ... of ... .
Symbol.iterator
// A method converting an object to a primitive value.
Symbol.toPrimitive
// A string value used for the default description of an object.
//  Used by Object.prototype.toString().
Symbol.toStringTag
// An Array of string values that are property values. These are excluded from the
//  with environment bindings of the associated objects.
Symbol.unscopables

SYMBOLS: WELL-KNOWN SYMBOLS EXAMPLE

let obj = {
  data: [ 'hello', 'world' ],
  [Symbol.iterator]: function() {
    const self = this;
    let index = 0;
    return {
      next() {
        if (index < self.data.length) {
          return {
            value: self.data[index++]
          };
        } else {
          return { done: true };
        }
      }
    };
  }
};

for (let elem of obj) {
  console.log(elem);
}

// hello
// world

SYMBOLS: GLOBAL SYMBOL REGISTRY

  • Symbols created using Symbol() are not globally unique or shared across the whole code base
// Creates a symbol available in a global symbol registry list.
Symbol.for('foo'); // Creates a new global symbol.
Symbol.for('foo'); // Retrieves the already created symbol.

// Same global symbol, but not locally.
Symbol.for('bar') === Symbol.for('bar'); // true
Symbol('bar') === Symbol('bar'); // false

// The key is also used as the description.
var globalSym = Symbol.for('bar');
console.log(globalSym.toString()); // Symbol(bar)
console.log(Symbol.keyFor(globalSym)); // bar

var localSym = Symbol();
Symbol.keyFor(localSym); // undefined

// Well-known symbols are not symbols registered in the global symbol registry.
Symbol.keyFor(Symbol.iterator) // undefined

Modules

// /lib/myModule.js
function sum(a, b) {
  return a+b;
}
const PI = 3.14159265359;

export sum, PI; 
import {sum as add} from 'lib/myModule';
console.log(add(1, 2)); // 3
import myModule from 'lib/myModule';
console.log(myModule.PI); // 3.14159265359
import objectName from 'file-name';
import {member} from 'file-name';
import {member as alias} from 'file-name';
import {member1, member2} from 'file-name';
import {member1, member2 as alias2, [...]} from 'file-name';
import objectName, {member [, [...]]} from 'file-name';
import 'file-name' as objectName;
export name1, name2, ..., nameN;
export *; 

Modules

// /lib/myModule.js
export default function(a, b) {
  return a+b;
}
import sum from 'lib/myModule';
console.log(sum(1, 2)); // 3

OOP

  • OOP:
    • Programming paradigm which creates models based on the real world
    • Benefits:
      • Simpler development (flexibility)
      • Easier understanding (maintainability)
  • Javascript:
    • Object-oriented to its core
    • Prototype-oriented / instance-oriented / class-less
    • Every object is an instance of the Object object

BASIC TERMINOLOGY (I)

  • Namespaces: 
var myNameSpace = myNameSpace || {};
myNameSpace.mySubNameSpace = myNameSpace.mySubNameSpace || {};
  • Classes, constructors, properties and methods:
// In ECMAScript <= 5 there was no class statement :(
var Person = function(name) { this.name = name; };
Person.prototype.getName = function() { return this.name; };
var paco = new Person('Paco');
console.log(paco.name); // Paco
console.log(paco.getName()); // Paco

var getPacosName = paco.getName;
getPacosName(); // undefined or TypeError if strict mode
var unInitializedPerson =
    Object.create(Person.prototype,
                  { age: { writable: true, value: -1}});
console.log(unInitializedPerson.name); // undefined
console.log(unInitializedPerson.getName()); // undefined
console.log(unInitializedPerson.age); // -1

BASIC TERMINOLOGY (II)

  • Abstraction (composition and specialisation), inheritance (single, multiple via mixin), encapsulation: 
// In ECMAScript <= 5 there was no extends statement :(
var Person = function(name) { this.name = name; };
// Encapsulation:
Person.prototype.getName = function() { return this.name; };
var Student = function(name, subject) {
    Person.call(this, name);
    this.subject = subject;
}
// Composition:
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
// Polyformism:
Student.prototype.getName = function() { return this.name };
Student.prototype.getSubject = function() { return this.subject; };
// Super class functions could be overwritten
var pepe = new Student('Pepe', 'Literature');
console.log(pepe.getName()); // Pepe
console.log(pepe.getSubject()); // Literature
// For multiple inheritance we need:
//  - Multiple SuperClass.call(this, arg1, ..., argN);
//  - Super class' prototype functions -> sub class' prototype

Object literals

let human = {
  // New method-like syntax.
  think(about) { return 'Thinking about ' + about; }
};
// Deprecated formula
let man = {
  __proto__: human, // Deprecated
  party() {
    // Super calls.
    return super.think('nothing'); // Useful when overloading
  }
};
console.log(man.think('women')); // Thinking about women
console.log(man.party()); // Thinking about nothing
// Preferred formula
let woman = {};
Object.setPrototypeOf(woman, human);
console.log(woman.think('men')); // Thinking about men
// ECMAScript 5 style.
var worker = Object.create(human);
worker.work = function(job) {
  return this.think(job) + ' and working on it';
};
console.log(worker.work('programming'));
// Thinking about programming and working on it

Classes

class Human {
  // Constructor.
  constructor(fullName) {
    this.fullName = fullName;
  }
  // New method-like syntax.
  think(about) { return this.fullName + ' is thinking about ' + about; }
  static eats(anything) { return true; }
}

console.log(new Human('René Descartes').think('existence'));
// René Descartes is thinking about existence
console.log(Human.eats('meat')); // true
// ECMAScript 5 equivalent.
function HumanEC5(fullName) {
  this.fullName = fullName;
}
HumanEC5.prototype.think = function(about) {
  return this.fullName + ' is thinking about ' + about;
};
HumanEC5.eats = function(anything) { return true; };

console.log(new HumanEC5('René Descartes').think('existence'));
// René Descartes is thinking about existence
console.log(HumanEC5.eats('meat')); // true

Classes: Inheritance

class Human {
  // Constructor.
  constructor(name) {
    this.name = name;
  }
  // New method-like syntax.
  think(about) {
    return this.name + ' is thinking about ' + about;
  }
}
class Man extends Human {
  // Constructor.
  constructor(name) {
    // Super calls.
    super(name);
    this.gender = 'male';
  }
  // New method-like syntax.
  party() {
    return this.think('nothing'); // return super.think('nothing');
  }
}

console.log(new Man('René Descartes').party());
// René Descartes is thinking about nothing

Classes: Inheritance

// ECMAScript 5 equivalent

function HumanEC5(fullName) {
  this.fullName = fullName;
}
HumanEC5.prototype.think = function(about) {
  return this.fullName + ' is thinking about ' + about;
};

function ManEC5(fullName) {
  // Complex inheritance tracking
  HumanEC5.call(this, fullName); // No super calls
  this.gender = 'male';
}
ManEC5.prototype = Object.create(HumanEC5.prototype);
ManEC5.prototype.constructor = ManEC5;
ManEC5.prototype.party = function() {
  return HumanEC5.prototype.think.call(this, 'nothing'); // No super calls
};

console.log(new ManEC5('René Descartes').party());
// René Descartes is thinking about nothing

Classes: EXERCISE

// Create a class 'Shape' whose constructor takes the number of edges argument.
// Create a class 'Triangle' which extends 'Shape' and has a static method 'is'
//  which checks if the shape passed as argument is a Triangle.
// Create a class 'Square' which extends 'Shape' and has a static method 'is'
//  which checks if the shape passed as argument is a Square.
class Shape {
  constructor(edges) {
    if (edges < 3) throw new Error('A shape should have more than 2 edges');
    else this.edges = edges;
  }
}
class Triangle extends Shape {
  constructor() { super(3); }
  static is(triangle) {
    return (triangle instanceof Triangle) && triangle.edges === 3;
  }
}
class Square extends Shape {
  constructor() { super(4); }
  static is(square) {
    return (square instanceof Square) && square.edges === 4;
  }
}

Standard library

  • Map:
    • Iterable {key (any type): value (any type)}-based object
  • Set:
    • Iterable collection of unique values (distinct types)
  • WeakMap:
    • Iterable {key (only objects): value}-based object
    • Weak references to the keys (objects) -> Could be garbage collected if no other reference
  • WeakSet:
    • Iterable collection of unique objects (only objects)
    • Weak references to the objects -> Could be garbage collected if no other reference

Standard library

Standard library: EXERCISE (SET UNION)

// Create a function called 'union' which calculates the union of the Sets
//  passed as arguments.
union(set1, set2, set3);
// set1 ∪ set2 ∪ set3
// SOLUTION:
function union(...sets) {
  let finalSet = new Set();
  for (let set of sets) {
    finalSet = new Set([...finalSet, ...set]);
  }
  return finalSet;
}

var set1 = new Set([1, 2, 3]);
var set2 = new Set([3, 4, 5]);
var set3 = new Set([4, 5, 6]);
console.log(union(set1, set2, set3)); // Set [ 1, 2, 3, 4, 5, 6 ]

Standard library: EXERCISE (SET intersection)

// Create a function called 'intersection' which calculates the intersection
//  of the Sets passed as arguments.
intersection(set1, set2, set3);
// set1 ∩ set2 ∩ set3
// SOLUTION:
function intersection(...sets) {
  let finalSet = sets[0];
  for (let i = 1; i < sets.length; i++) {
    finalSet = new Set([...sets[i]].filter(
      entry => finalSet.has(entry)
    ));
  }
  return finalSet;
}

var set1 = new Set([1, 2, 3]);
var set2 = new Set([2, 3, 4]);
var set3 = new Set([3, 4, 5]);
console.log(intersection(set1, set2, set3)); // Set [ 3 ]

Standard library: EXERCISE (SET DIFFERENCE)

// Create a function called 'difference' which calculates the difference
//  between 2 sets.
intersection(set1, set2);
// set1 - set2, elements of set1 which are not in set2
// SOLUTION:
function difference(set1, set2) {
  return new Set([...set1].filter(entry => !set2.has(entry)));
}

var set1 = new Set([1, 2, 3]);
var set2 = new Set([2, 3, 4]);
console.log(difference(set1, set2)); // Set [ 1 ]

QUESTIONS???

THANKS!!! ;)

ECMAScript 6: The ES.next version of JS

By Germán Toro del Valle

ECMAScript 6: The ES.next version of JS

  • 3,748