By Francisco Ramos
During my backend times I was always doing frontend: HTML + CSS + JS
I was a real Web Developer
Since then, I've been doing only HTML + CSS + JavaScript
... but my passion is architectures, libraries, frameworks, code conventions, best practices...
I'm sort of a
JavaScript evangelist
Feature for scalable applications
// Profile.js
export var firstName = 'Francisco';
export var lastName = 'Ramos';
export var year = 1978;
// ProfileView.js
import {firstName, lastName} from './Profile';
import {year as yearOfBirth} from './Profile';
function setHeader(element) {
element.textContent = firstName + ' ' + lastName;
}
Exports and Imports
// lib/math.js
let PI = 3.14159; // not exported
export function sum(a, b) { return a+b; }
export function square(x) { return x*x; }
export function area(r) { return PI*(r^2); }
// main.js
module math from 'lib/math';
console.log(math.square(3));
// main2.js
import * as math from 'lib/math';
console.log(math.area(5));
Importing the whole module
// main3.js
import 'lib/math' as math;
console.log(math.sume(2, 5));
// lib/utils.js
export const PI = 3.14159;
export default {
func1() {},
func2() {},
func3() {}
};
// app.js
import {PI} from 'lib/utils';
import utils from 'lib/utils';
console.log(PI);
utils.func1();
Default exports
Re-exporting things
// something.js
export * from 'Profile';
export {sum, square} from 'lib/math';
// main.js
import 'something' as stuff;
console.log(stuff.firstName);
console.log(stuff.lastName);
console.log(stuff.year);
stuff.sum(2, 2);
stuff.square(4);
Extending JavaScript
Meta-programming in JavaScript
let Person = {
name: 'Fran',
age: 35
};
let PersonWrap = Proxy(Person, Interceptor);
let Interceptor = {
get(target, prop, receiver) {}, // personWrap.name
set(target, prop, val, receiver) {}, // personWrap.age = 36
delete(target, prop) {}, // delete personWrap.name
has(target, prop) {}, // 'name' in personWrap
enumerate (target) {} // for (let prop in personWrap) {}
};
... and much more
{
getOwnPropertyDescriptor: function(target,name) {}, // -> Object.getOwnPropertyDescriptor(proxy,name)
getOwnPropertyNames: function(target) {}, // -> Object.getOwnPropertyNames(proxy)
getPrototypeOf: function(target) {}, // -> Object.getPrototypeOf(proxy)
defineProperty: function(target,name, desc) {}, // -> Object.defineProperty(proxy,name,desc)
deleteProperty: function(target,name) {}, // -> delete proxy[name]
freeze: function(target) {}, // -> Object.freeze(proxy)
seal: function(target) {}, // -> Object.seal(proxy)
preventExtensions: function(target) {}, // -> Object.preventExtensions(proxy)
isFrozen: function(target) {}, // -> Object.isFrozen(proxy)
isSealed: function(target) {}, // -> Object.isSealed(proxy)
isExtensible: function(target) {}, // -> Object.isExtensible(proxy)
hasOwn: function(target,name) {}, // -> ({}).hasOwnProperty.call(proxy,name)
keys: function(target) {}, // -> Object.keys(proxy)
apply: function(target,thisArg,args) {}, // -> proxy(...args)
construct: function(target,args) {} // -> new proxy(...args)
}
Must watch: Proxies are awesome by Brendan Eich
No more callbacks
Pyramid of doom :-(
(function () {
function init() {
fetchHtml('http://html.json', function (data) {
buildDOM(data, function () { // async??
fetchContent('http://content.json', function (data) {
bindContent(data, function () {
alert("I'm done!!");
})
});
});
});
}
init();
})();
Refactored version :-\
// hoisting callbacks
(function () {
function onContentBound() { alert("I'm done!!"); }
function onContentFetched(data) { bindContent(data, onContentBound); }
function onDOMBuilt() { fetchContent('http://content.json', onContentFetched); }
function onHtmlFetched(data) { buildDOM(data, onDOMBuilt); }
function init() { fetchHtml('http://html.json', onHtmlFetched); }
init();
})();
Using promises :-)
(function () {
init()
.then(fetchHtml)
.then(buildDOM)
.then(fetchContent)
.then(bindContent)
.then(() => alert("I'm done!!") )
.catch(() => alert("Something when wrong!!") )
;
})();
... and if the promise is resolved before registering the callback, once we do it, it's called immediately.
API
/* Constructor */
new Promise(function (resolve, reject) {});
/* Methods */
Promise.prototype.then(onFulfilled, onRejected)
Promise.prototype.catch(onRejected) == Promise.prototype.then(null, onRejected)
/* Static methods */
Promise.resolve(value) // -> resolved Promise
Promise.reject(reason) // -> rejected Promise
Promise.all(...promises) // -> Promise that will resolve when all promises resolved
It's all about this
Arrows share the same lexical this as their surrounding code
var Form = {
init: function () {
this.$input = document.querySelector('input');
this.$button = document.querySelector('button');
this.attachEvents();
},
attachEvents: function () {
// ES3 - var that = this
var that = this; // scope variable for closure
document.addEventListener('click', this.$button, function (e) { that.onButtonClick(e) });
// ES5 - Function.prototype.bind
document.addEventListener('click', this.$button, this.onButtonClick.bind(this) );
// ES6 - arrow function
document.addEventListener('click', this.$button, e => this.onButtonClick(e) );
}
onButtonClick: function (e) { /* we do something when the use clicks on the button */ }
};
The new var
The problem
// ES3, ES5
var handlers = [];
for (var count = 0; count < 3; count++) {
handlers[count] = function () {
alert(count);
}
}
handlers[0](); // alerts "3"
handlers[1](); // alerts "3"
handlers[2](); // alerts "3"
// ES3, ES5
var handlers = [];
var count; // this variable is hoisted
for (count = 0; count < 3; count++) {
handlers[count] = function () {
alert(count);
}
}
handlers[0](); // alerts "3"
handlers[1](); // alerts "3"
Solution (a nasty one)
// ES3, ES5
var handlers = [];
for (var count = 0; count < 3; count++) {
(function (i) {
handlers[i] = function () {
alert(i)
};
})(count);
}
handlers[0](); // alerts "0"
handlers[1](); // alerts "1"
handlers[2](); // alerts "2"
// ES6
let handlers = [];
for (let count = 0; count < 3; count++) {
handlers[count] = function () {
alert(count);
}
}
handlers[0](); // alerts "0"
handlers[1](); // alerts "1"
... and much more
let x = 1; // let-definition
let(y = 2, z = 3) { // let-statement
// block where "x", "y" and "z" are visible
}
// scope where only "x" is visible
{ // new block scope
let i = 0; // will be visible within this block
}
let (
privVal = 5,
myPrivate = function () { /* do something private */ }
) {
var something = myPrivate();
function getVal() { return privVal; }
}
Handy tools to make our life easier
Set
Ordered list of values that cannot contain duplicates.
Nothing new if you come from Java or Phyton
let items = new Set();
items.add(5);
items.add("5");
items.add(5); // oops, duplicate - this is ignored
console.log(items.size); // 2
let items = new Set([1, 2, 3, 4, 5, 5, 5, 'Fran']);
console.log(items.size); // 6
consooe.log(items.has(2)); // true
items.delete('Fran');
consooe.log(items.size); // 5
Map
The basic idea is to map a value to a unique key,
any type of key, even objects
let myMap = new Map();
let key1 = 'bla';
let key2 = document.getElementById("myDiv");
myMap.set(key1, 'value1');
myMap.set(key2, metadata);
// later
let value = myMap.get('bla');
let meta = myMap.get(document.getElementById("myDiv"));
WeakMap
Same as Map objects, but the key must be an object.
There is a reason for that...
let wMap = new WeakMap();
let element = document.querySelector(".element");
wMap.set(element, "My Value");
console.log(wMap.get(element)); // "My Value"
// later - remove reference
element.parentNode.removeChild(element);
element = null; // the element gets garbage collected
console.log(wMap.size); // 0
A WeakMap holds only a weak reference to a key
... more efficient memory consumption :-)
co-routines/multi-task
... and what can I do with this ???
function *myGenerator() {
// some code here
yield myTask1();
yield myTask2();
yield myTask3();
// more code here
}
let g = myGenerator();
g.next(); // executes myTask1 returning result
g.next(); // executes myTask2 returning result
g.next(); // executes myTask3 returning result
More new syntax
var fibonacci = function* () {
let [prev, curr] = [0, 1];
for (;;) { // infinite iteration
[prev, curr] = [curr, prev + curr];
yield curr;
}
};
for (let val of fibonacci()) {
if (val > 1000) break;
console.log(val); // 1, 2, 3, 5, 8...
}
var fib = fibonacci();
console.log(fib.next()); // 1
console.log(fib.next()); // 2
console.log(fib.next()); // 3
console.log(fib.next()); // 5
console.log(fib.next()); // 8
function* keyValue(obj) {
for (let k in obj) {
yield [k, obj[k]];
}
}
let Person = {
name: 'Fran',
age: 36,
country: 'Spain'
}
for (let [key, val] of keyValue(Person)) { ... }
let userTasks = (function* () {
// Coroutines that return promises
let a = yield $.ajax('http://get/user');
let b = yield $.ajax('http://process/user', a);
yield $.ajax('http://save/user', b);
})();
userTasks.next() // gets the user
.then( user => {
// we do something with the user and then...
return userTasks.next(user); // processes the user
})
.then( user => {
// we do something else after the user has been processed and then...
return userTasks.next(user); // saves the user
})
.then( user => {
// final operations here
})
;
Elegant way of array processing
// ES6
let arr = [1, 2, 3, 4, 5];
//1. Array filter
let mult2 = [for (x of arr) if (x % 2 == 0) x];
console.log(mult2); // [2, 4]
//2. Array map
let pow2 = [for (x of arr) Math.pow(x, 2)];
console.log(pow2); // [1, 4, 9, 16, 25]
// ES5
var arr = [1, 2, 3, 4, 5];
//1. Array filter
var mult2 = arr.filter(function (val, i) { return val % 2 == 0; });
console.log(mult2); // [2, 4]
//2. Array map
var pow2 = arr.map(function (val, i) { return Math.pow(val, 2); });
console.log(pow2); // [1, 4, 9, 16, 25]
Picked up from languages such as Python or CoffeeScript
Saving you lots of typing
Destructuring arrays
let [x, y] = [10, -10, 5];
console.log(x, y); // 10, -10;
let [a, , b] = [1, 2, 3];
console.log(a, b); // 1, 3;
// variables swapping
[a, b] = [b, a];
Destructuring objects
let user = {
name: 'Fran',
age: 36,
location: [15, -20]
};
let {name: n, age: a, location: l} = user;
console.log(n, a, l[0], l[1]); // Fran, 36, 15, -20
// or even shorter
let {name, age, location: [lat, lng]} = user;
console.log(name, age, lat, lng); // Fran, 36, 15, -20
Destructuring of function arguments
// ES3, ES5
function ajax (config) {
var url = config.url;
var data = config.data || {};
var method = config.method || 'get';
var complete = config.complete || new Function;
// this is too "noisy" :-/
}
ajax({
url: 'http://www.whatever.com/method',
data: { ... },
method: 'post',
complete: function () { ... }
});
// ES6
function ajax ({ url, data, method, complete }) {
console.log(url, data, method, complete);
}
Even more typing saving
// ES3, ES5
function getTypeId() { return 'passport'; };
var Person = {
name: 'Fran',
getTypeId: getTypeId,
sayName: function () {
alert('My name is ' + this.name);
}
};
Person[getTypeId()] = '12341234x';
Too much typing. We need something simpler...
// ES6
function getTypeId() { return 'passport'; };
var Person = {
name: 'Fran',
getTypeId, // shorthand for getTypeId: getTypeId
sayName() { alert('My name is ' + this.name); },
[getTypeId()] = '12341234x' // computed property names
};
Object-based design
var vehicle = {
color: '',
speed: 0,
accelerate(up) { this.speed += up; },
slowDown(down) { this.speed -= down; }
};
var Ferrari = {
__proto__: vehicle,
color: 'red',
accelerate(up) { super.accelerate(up + 30); }
};
var plane = {
__proto__: vehicle,
color: 'white',
altitude: 0,
takeOff(up) {
super.accelerate(300);
this.altitude += up;
},
fly() { this.takeOff(10000); }
};
Fixing "misdesign"
// ES3, ES5
function foo (x, y) {
var rest = [].slice.call(arguments, 2);
// (arguments instanceof Array) === false
// now "rest" is a real array
rest.slice( ... );
rest.map( ... );
}
// ES6
function foo (x, y, ...rest) {
// "rest" is a real array!!
for (let i of rest) { ... }
rest.slice( ... );
rest.filter( ... );
rest.map( ... );
}
It gets weirder though
// ES3, ES5
function MyType() {
var args = [].slice.call(arguments);
this.processArgs.apply(this, args); // I mean, WTF!? O_O
}
MyType.prototype.processArgs = function (arg1, arg2) {
var rest = [].slice.call(arguments, 2);
};
// ES6
function MyType (...args) {
this.processArgs(...args);
}
MyType.prototype.processArgs = function (arg1, arg2, ...rest) {
// "rest" is an array
}
Wait, it gets better...
function storeUserDetails(name, age, country) {
$.ajax('http://www.example.com/storeUser', {...});
}
let userInfo = ['Fran', 36, 'Spain'];
// ES5
storeUserDetails.apply(null, userInfo);
// ES6
storeUserDetails(...userInfo);
Complementing for-in
TO-DO
Function params values by default
TO-DO
Let's make everybody happy
This is what a class will look like
class MyType { // new keyword "class"
// "strict mode";
private privAttr; // let privAttr = Symbol();
static someProp = true; // MyType.someProp = true;
constructor(arg1, arg2) { // function MyType() {}
public pubAttr = arg1; // this.pubAttr = arg1;
this.privAttr = arg1 * arg2;
}
public pubMethod() { // MyType.prototype.pubMethod = function () {}
return [MyType.someProp, this.privAttr];
}
}
Let's have a look at the inheritance
class MySubType extends MyType { // MySubType.prototype = Object.create(MyType.prototype);
constructor(arg1, arg2) {
super(arg1); // MyType.call(this, arg1)
}
pubMethod (arg1) {
super.pubMethod(arg1); // MyType.prototype.pubMethod.call(this, arg1)
}
get x() {} // Object.defineProperty(this, 'x', { get: function() {} })
set y() {} // Object.defineProperty(this, 'y', { set: function() {} })
}