ECMASCRIPT 6
Slides: http://bit.ly/ecmascript6
Live at http://bit.ly/ecmascript6-live
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:
- Events:
- Implement the EventTarget interface (front-end)
- Inherit from the EventEmitter object (Node.js)
- Callbacks:
- Continuation-passing style
- Pyramid of doom or callback hell
- Complex error handling
- Events:
- 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
- Introspection:
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
- Object.is()
- Improved String manipulation:
- Improved Array manipulation:
- Improved Number manipulation:
- Improved Math manipulation:
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