Mettez votre JavaScript à jour !
Jordane Grenat @JoGrenat
Historique
Abandon d'ECMAScript 4
Décembre 1999 : ECMAScript 3
Décembre 2009 : ECMAScript 5
Mi-2015 : ECMAScript 6
Mi-2016 : ECMAScript 7
...
5 stades
- Stade 0 : Strawman
-
Stade 1 : Proposal
-
Stade 2 : Draft
-
Stade 3 : Candidate
-
Stade 4 : Finished
Bienvenue dans
ES2015
Let et const
let color = 'blue';
// Error
let color = 'red';
const color = 'red';
// Error
color = 'blue';
Merci de jeter vos var à la poubelle en sortant...
Objets
let firstName = 'Marcel';
let man = {
firstName,
getJob() {
return this.job;
},
['j' + 'o' + 'b']: 'Writer'
};
Arrow functions
let numbers = [1, 2, 3, 4];
let squares = numbers.map((nb) => {
return nb * nb;
});
// squares = [1, 4, 9, 16]
Syntaxe :
(arg1, arg2) => { instructions }
let numbers = [1, 2, 3, 4];
let squares = numbers.map(nb => {
return nb * nb;
});
// squares = [1, 4, 9, 16]
Syntaxe :
arg1 => { instructions }
let numbers = [1, 2, 3, 4];
let squares = numbers.map(nb => nb * nb);
// squares = [1, 4, 9, 16]
Syntaxe :
arg1 => instruction
Mais si on retourne un objet ?
let numbers = [ 1, 2, 3, 4 ];
let squares = numbers.map(nb =>
{ result: nb * nb }
);
// squares = [
// undefined,
// undefined,
// undefined,
// undefined
// ]
On rajoute juste des parenthèses
let numbers = [1, 2, 3, 4];
let squares = numbers.map(nb =>
({ result: nb * nb })
);
// squares = [
// 1,
// 4,
// 9,
// 16
// ]
let mathObject = {
numbers: [ 1, 2, 3, 4 ],
m: 3,
getMultiples() {
return this.numbers.map(nb => nb * this.m);
}
};
// mathObject.getMultiples() = [ 3, 6, 9, 12 ]
Important !
Le this est lié de façon lexicale !
Classes
Uniquement du sucre syntaxique
class Car {
constructor(brand) {
this.brand = brand;
}
displayBrand() {
console.log('Brand: ' + this.brand);
}
}
let ferrari = new Car('Ferrari'); // "new" is mandatory
ferrari.displayBrand(); // displays "Brand: Ferrari"
// ES6
class Car {
constructor(brand) {
this.brand = brand;
}
displayBrand() {
console.log('Brand: ' + this.brand);
}
}
// ES5
var Car = function(brand) {
this.brand = brand;
};
Car.prototype.displayBrand = function() {
console.log('Brand: ' + this.brand);
};
// ES6
class Audi extends Car {
constructor() {
super('Audi');
}
}
// ES5
var Audi = function() {
this.brand = 'Audi';
};
Audi.prototype = Object.create(Car.prototype);
Héritage
Attention, pas d'héritage multiple !
// ES6
class Car {
constructor(brand) {
this.brandName = brand;
}
set brand(brand) {
this.brandName = brand;
}
get brand() {
return this.brandName.toLowerCase();
}
};
let car = new Car('Audi');
car.brand = 'ToYoTa';
console.log(car.brand); // "toyota"
Getter / Setter
Getter / Setter
// ES5
var Car = function(brand) {
var brandName = brand;
Object.defineProperty(this, 'brand', {
set: function(brand) {
brandName = brand.toUpperCase();
},
get: function() {
return brandName.toLowerCase();
}
});
};
var car = new Car('Audi');
this.brand = 'ToYoTa';
console.log(this.brand);
// ES6
class Audi extends Car {
static introduce() {
console.log("Hi, I'm a car!");
}
}
Audi.introduce(); // "Hi, I'm a car!"
Méthodes statiques
// ES5
var Car = function() {};
Car.introduce = function() {
console.log("Hi! I'm a car!");
};
Car.introduce(); // "Hi! I'm a car!"
Destructuring
Permet de décomposer
des tableaux
let ranks = ['Marc', 'Laure', 'Patricia', 'Jérôme'];
let [first, second, third] = ranks;
console.log(first); // "Marc"
console.log(second); // "Laure"
console.log(third); // "Patricia"
let [value = 'Default value'] = [];
console.log(value); // "Default value"
Et des objets
let luc = {
lastName: 'Pignon',
age: 31
};
let {age: ageOfLuc} = luc;
console.log(ageOfLuc); // 31
let {lastName} = luc;
console.log(lastName); // "Pignon"
let {birthYear = 1985} = luc;
console.log(age); // 1985
Très utile pour les
paramètres de fonction
function drawCircle({ x = 100, y = 100, r = 100 }) {
// Draw a circle.
// If x, y or r are not specified,
// they have a default value
}
Ou pour échanger
les valeurs de 2 variables
let a = 5;
let b = 3;
[b, a] = [a, b];
Rest et spread
Opérateur
...
Utilisé en temps que "rest" = "tout le reste"
let ranks = ['Marc', 'Laure', 'Patricia', 'Jérôme'];
let [first, second, ...others] = ranks;
console.log(others); // ['Patricia', 'Jérôme']
let luc = { lastName: 'Pignon', age: 31 };
let { lastName, ...others } = luc;
Ne fonctionne pas avec les objets
NOK
Fonctionne aussi avec les
paramètres d'une fonction
function add(...args) {
// args est un vrai tableau,
// contrairement à arguments
return args.reduce((a, b) => a + b, 0);
}
console.log(add(1, 2, 6, 4)); // 13
function add(a, b, c) {
return a + b + c;
}
let numbers = [6, 4, 10];
console.log( add(...numbers) );
Utilisé en temps que "spread"
Template literals
En Javascript, les chaînes complexes sont un cauchemar...
var name = "Marcel";
var string = "I am " + name + "\n" +
"and I have " + (3 + 5) + " apples\n" +
"in my kitchen!";
// Or
var string = "I am " + name + " \n\
and I have " + (3 + 5) + " apples \n\
in my kitchen!";
ES6 introduit les templates literals
let name = "Marcel";
let string = `I am ${name}
and I have ${3 + 5} apples
in my kitchen!`;
Et les tags...
function tagFunction(strings, ...values) {
console.log(strings[0]); // "Hello "
console.log(values[0]); // "Marcel"
console.log(strings[1]); // "! I'm "
console.log(values[1]); // "Linda"
console.log(strings[2]); // ""
return "replaced";
}
const you = "Marcel";
const me = "Linda";
let string = tagFunction`Hello ${you}! I'm ${me}`;
console.log(string); // "replaced"
Ex: Internationalisation
const translations = {
fr: {
'Hello [0]! Date: [1]': 'Date : [1]. Bonjour [0] !'
},
de: {
'Hello [0]! Date: [1]': 'Guten Tag [0]! Datum: [1]'
}
};
let locale = 'fr';
Ex: Internationalisation
function i18n([start, ...strings], ...values) {
const localTranslations = translations[locale] || {};
const key = strings.reduce((acc, string, i)
=> `${acc}[${i}]${string}`, start);
// Exemple: Hello [0]! Date: [1]
}
return values.reduce((result, value, i) => {
}, localTranslations[key] || key);
return result.replace(`[${i}]`, value);
if (value instanceof Date) {
value = value.toLocaleString(locale);
}
Ex: Internationalisation
const name = 'Rémi';
let french = i18n`Hello ${name}! Date: ${new Date()}`;
// "Date : 15/02/2017 à 20:20:46. Bonjour Rémi !"
locale = "de";
let german = i18n`Hello ${name}! Date: ${new Date()}`;
// "Guten Tag Rémi! Datum: 15.2.2017, 20:27:14"
Symbols
Un nouveau type primitif
let symbol = Symbol();
Génère des ID uniques pouvant servir de clé pour des objets
Les clés sont des objets Symbol
Pas de conflits avec d'autres attributs
Ajout de nouvelles fonctionnalités
- Symbol.iterator
- Symbol.match
- Symbol.replace
- Symbol.search
- Symbol.split
- Symbol.toStringTag
- ...
Iteratérables et for...of
ES6 introduit la notion d'itérables
Un élément itérable implémente la fonction [Symbol.iterator]()
Cette fonction doit retourner un objet contenant une fonction next()
let myIterableObject = {
[Symbol.iterator]() {
return {
next() {
// code
}
}
}
}
Cette fonction next doit retourner un objet du type :
{
value: "Value of the iteration",
done: false
}
On peut les itérer avec l'opérateur spread
let values = [...myIterableObject];
On peut aussi utiliser la décomposition
let [first, second] = myIterableObject;
Ou avec un for...of
for(let value of myIterableObject) {
console.log(value);
}
Que peut-on itérer ?
- Des chaines de caractères
- Des tableaux
- Ce qui implémente Symbol.iterator()
- Des générateurs
- Map / Set
Promises
Une promesse est un objet représentant une valeur et qui peut avoir trois états :
- En attente de résultat
- Complétée avec succès
- En erreur
Une promesse est utilisée pour faciliter et clarifier les appels asynchrones
// Let's say getFile is a function that
// returns a promise
getFile(url).then(value => {
// Code
}, error => {
// Error code
});
getFile(url).catch(error => {
// Error code
});
Utiliser une promesse
Chaîner des promesses
getProfile(personId).then(profile => {
return profile.name;
}).then(name => {
// Code
});
getFile(url).then(content => {
throw new Error();
}).catch(error => {
// Catch the error
});
getUrlFromBDD(id).then(url => {
return getFile(url):
}).then(content => {
// Called after getFile completion
});
Chaîner des promesses
function() {
let promise = new Promise((resolve, reject) => {
myAsynchronousOperation((err, value) => {
if(err) {
reject(err);
} else {
resolve(value);
}
});
});
return promise;
}
Créer des promesses
// Resolved when all promises are resolved
Promise.all(iterable);
Promise API
// Resolved when one of the promises is resolved
Promise.race(iterable);
// Return a promise that is immediatly fulfilled
Promise.resolve(value);
// Return a promise that is immediatly rejected
Promise.reject(error);
Generators
function *countTo3() {
yield 1;
yield 2;
yield 3;
}
let iterator = countTo3();
console.log(iterator.next()); // Displays: { value: 1, done: false }
console.log(iterator.next()); // Displays: { value: 2, done: false }
console.log(iterator.next()); // Displays: { value: 3, done: false}
console.log(iterator.next());
// Displays: { value: undefined, done: true }
function *countTo3() {
yield 1;
yield 2;
return 3;
}
let it = countTo3();
it.next();
it.next();
console.log(it.next()); // Displays { value: 3, done: true }
Example: Fibonacci
function* fibonacci() {
let a = 0;
let b = 1;
yield 1;
while(true) {
[a, b] = [b, a + b];
yield b;
}
}
var g = fibonacci();
console.log(g.next().value); // Displays: 1
console.log(g.next().value); // Displays: 1
console.log(g.next().value); // Displays: 2
console.log(g.next().value); // Displays: 3
console.log(g.next().value); // Displays: 5
Un générateur est itérable
function* countTo3() {
yield 1;
yield 2;
yield 3;
}
let values = [...countTo3()];
for(value of countTo3()) {
console.log(value);
}
function *sum() {
let sum = 0;
while(true) {
sum += yield sum;
}
}
let it = sum();
it.next(); // Initialize generator - can't accept input
it.next(3); // Returns: 3
it.next(18); // Returns: 21
it.next() peut prendre
un argument qui sera retourné par yield.
function *gen1() {
yield 2;
yield 3;
}
Un générateur peut déléguer avec yield*
function *gen2() {
yield 1;
yield* gen1();
yield 4;
}
let it = gen2();
it.next(); // Returns 1
it.next(); // Returns 2
it.next(); // Returns 3
it.next(); // Returns 4
Exemple : parcours d'un arbre
// Source: http://www.2ality.com/2015/03/es6-generators.html
class BinaryTree {
constructor(value, left=null, right=null) {
this.value = value;
this.left = left;
this.right = right;
}
/** Prefix iteration */
* [Symbol.iterator]() {
yield this.value;
if (this.left) {
yield* this.left;
}
if (this.right) {
yield* this.right;
}
}
}
Exemple : l'asynchrone avec les coroutines
getUrlFromBDD(id).then(url => {
return getFile(url):
}).then(content => {
console.log(content);
});
co(function*(id) {
const url = yield getUrlFromBDD(id);
return yield getFile(url);
}).then(content => {
console.log(content)
});
npm install co
Deux autres méthodes pour un générateur
- it.return() arrête le générateur
- it.throw() envoie une erreur au générateur
it.return(value)
Effectue un return value
à la place du yield
it.throw(error)
Effectue un throw error
à la place du yield
function* myGenerator() {
try {
yield;
yield;
} catch(error) {
console.log(`Error: ${error}`);
} finally() {
console.log("this is not the end");
yield;
}
}
Des explications plus détaillées et de nombreux exemples
Defaults values
function add(a = 5, b = 5) {
return a + b;
}
console.log(add()); // Display 10
console.log(add(3)); // Display 8
console.log(add(3, 4)); // Display 7
Map / Set
WeakMap / WeakSet
Map
let map = new Map();
map
.set('key1', 'value1')
.set('key2', 'value2');
map.get('key1'); // Returns: 'value1'
map.size; // Equals: 2
map.has('key1'); // Returns: true
map.delete('key1');
map.clear();
let map2 = new Map([['key1', 'value1'], ['key2', 'value2']]);
Tout peut être une clé
map
.set({}, 'value')
.set(Symbol(), 'value')
.set(34, 'value')
.set(/regex/, 'value')
.set(() => {}, 'value')
.set(NaN, 'value')
.set(true, 'value');
Map est itérable
for(let [key, value] of map) {
// Code
}
for(let value of map.values()) {
// Code
}
for(let key of map.keys()) {
// Code
}
map.forEach((value, key, collection) => {
// Code
});
WeakMap
- Une Map qui n'influe pas sur le garbage collector
- Non itérable
- get()
- set()
- has()
- delete()
Exemple: Attributs privés
const PRIVATE_MEMBER = new WeakMap();
class MyClass {
constructor() {
PRIVATE_MEMBER.set(this, 'value');
}
displayPrivateMember() {
console.log(PRIVATE_MEMBER.get(this));
}
}
Set
let set = new Set();
set
.add('value1')
.add('value2');
set.has('value1'); // Returns: true
set.size; // Equals: 2
set.delete('value1');
set.clear();
let set2 = new Set(['value1', 'value2']);
Set est itérable
for(let value of set) {
// Code
}
map.forEach((value, key, collection) => {
// Code
});
Pour uniformiser les interfaces de Set et Map, ces deux méthodes existent :
Dans le cas d'un Set, key = value
- set.entries()
- set.keys()
WeakSet
-
N'influe pas sur le garbage collector
- N'est pas itérable
- add()
- has()
- delete()
Modules
Exports nommés
export function name1() {
};
export const name2 = 'value';
export let name3 = 1;
export class MyClass {};
let name4 = () => {};
let name5 = 5;
export { name4, name5 };
export { name4 as arrowFunc, name5 as five };
Export par défaut
export default 'value';
Importer
import theDefaultExport from 'lib';
import { name1, name2 } from 'lib';
import theDefaultExport, { name1 } from 'lib';
import { name1 as otherName } from 'lib';
import * as myLib from 'lib';
import 'lib';
Nouveautés diverses
Identifiers
// Accents et autres caractères spéciaux
let réseauFrançais;
// Unicode
let \u0061;
let \u{61};
let 𐊧;
let ♥;
-
Meilleure gestion de l'Unicode
-
Meilleure gestion du binaire
- Meilleure gestion de l'octal
\u{20BB7}
0b111110111
0o767
Nouveautés mathématiques
Number.EPSILON;
Number.isNaN(NaN); // true
Number.isFinite(-Infinity); // false
Number.parseInt('11', 8); // 9
Number.parseFloat('11.3'); // 11.3
Nouveautés de String
'my string'.startsWith('my'); // true
'my string'.endsWith('ng'); // true
'my string'.includes('st'); // true
'hip'.repeat(3); // "hiphiphip"
Nouveautés de Array
// Retourne un vrai tableau
Array.from(document.querySelectorAll('*'));
Array.of(1, 2, 3);
[1, 2, 3].fill(7, 1); // [1,7,7]
[1, 2, 3].find(x => x == 3) // 3
[1, 2, 3].findIndex(x => x == 3) // 2
[1, 2, 3, 4, 5].copyWithin(3, 0) // [1, 2, 3, 1, 2]
[1, 2, 3].entries();
[1, 2, 3].keys();
[1, 2, 3].values();
Proxy
Les proxies permettent d'intercepter des opérations effectuées sur des objets
Ces opérations peuvent être :
- L'accès à une propriété en lecture
- L'accès à une propriété en écriture
- Appel de méthode
- Suppression d'une propriété
- La création d'une instance
- ...
let proxy = new Proxy(target, handler);
L'objet sur lequel on va intercepter
Notre objet intercepteur
const handler = {
get(target, key, receiver) {
return 'random_value';
}
};
const emptyObject = {};
const proxy = new Proxy(emptyObject, handler);
console.log(proxy.randomProperty); // 'random_value'
let {proxy, revoke} = Proxy.revocable(target, handler);
revoke();
console.log(proxy.test); // TypeError: Revoked
Proxy révocable
On passe à ES2016 ?
Array.prototype.includes
const names = ['Angélique', 'Jérôme', 'Luc'];
names.includes('Angélique'); // true
~names.indexOf('Angélique');
names.indexOf('Angélique') !== -1;
Exponentiation operator
const x = 2 ** 10; // 1024
const x = Math.pow(2, 10);
Support
- ES2015 : kangax.github.io/compat-table/es6/
- ES2016+ : kangax.github.io/compat-table/es2016plus/
- Node : node.green
Un peu d'ES2017 ?
Async Functions
Une fonction async peut
utiliser le mot-clé
await pour attendre le résultat d'une opération
Une fonction async renvoie une promesse
// ES7
async function loadFileContent(id) {
let url = await getUrlFromBDD(id);
return await getFile(url);
}
const loadFileContent = co.wrap(function*(id) {
const url = yield getUrlFromBDD(id);
return yield getFile(url);
});
Shared memory
and atomics
const worker = new Worker('worker.js');
// To be shared
const sharedBuffer = new SharedArrayBuffer( // (A)
10 * Int32Array.BYTES_PER_ELEMENT); // 10 elements
// Share sharedBuffer with the worker
worker.postMessage({sharedBuffer}); // clone
// Local only
const sharedArray = new Int32Array(sharedBuffer); // (B)
// main.js
console.log('notifying...');
Atomics.store(sharedArray, 0, 123);
// worker.js
while (Atomics.load(sharedArray, 0) !== 123) ;
console.log('notified');
En vrac
Objets
const luc = {
name: 'Luc',
age: 31
};
Object.values(luc); // ['Luc', 31]
Object.entries(luc); // [['name', 'Luc'], ['age', 31]]
Padding
"test".padStart(10); // " test"
"test".padEnd(10); // "test "
Ca évitera de reposer sur left-pad...
Trailing comas
function sum(a, b,) {
return a + b;
}
sum(3, 5,);
function sum(
a,
b,
) {
return a + b;
}
Et si on allait
plus loin encore ?
Performances
SIMD.JS
let var1 = new SIMD.Float32x4(1, 2, 3, 4);
let var2 = new SIMD.Float32x4(5, 6, 7, 8);
let var3 = SIMD.Float32x4.add(var1, var2);
// float32x4[6,8,10,12]
Simple Instruction, Multiple Data
=> Parallélisme
WebAssembly
Nouveau format compilé pour le web
Decorators
Une fonction qui agit sur une classe ou une propriété pour agir dessus et étendre son comportement
class Logger {
@deprecated()
warning() {
// ...
}
}
const logger = new Logger();
logger.warning('Whatever');
// Displays in console : "'warning' is deprecated.
function deprecated(target, name, descriptor) {
descriptor.value = function(...args) {
console.warn(`${name} is deprecated.`);
descriptor.value.apply(this, args);
};
return descriptor;
}
let description = {
type: 'method',
value: specifiedFunction,
enumerable: false,
configurable: true,
writable: true
};
class Logger {
@deprecated('warn')
warning() {
// ...
}
}
const logger = new Logger();
logger.warning('Whatever');
// Displays in console : "'warning' is a deprecated method.
// You should consider using 'warn' instead.
function deprecated(newMethod) {
return (target, name, descriptor) => {
descriptor.value = function(...args) {
console.warn(`${name} is deprecated.
You should consider using '${newMethod}.`);
descriptor.value.apply(this, args);
};
return descriptor;
}
}
On peut décorer
- Classes
- Propriétés
- Méthodes
- Getters / setters
Class properties
class MyClass {
myProperty = 1;
static myStaticProp = 42;
}
Dynamic imports
import('./path/to/my/module.js')
.then(module => {
module.doSomething();
})
.catch(err => {
console.error(err);
});
Observables
"Streams de données"
const observable = new Observable(observer => {
let i = 1;
const stopper = setInterval(() => observer.next(++i), 1000);
return () => clearInterval(intervalId);
});
Sur ce flux, on peut utiliser les fonctions habituelles de la programmation fonctionnelle
Map
Filter
ForEach
On peut souscrire à un Observable
const observer = {
start(subscription) {},
next(value) {},
error(error) {},
complete(complete) {}
};
const obs = observable.subscribe(observer);
obs.unsubscribe();
const observable = Observable.of(1, 2, 3);
const array123 = [1, 2, 3];
const observable2 = Observable.of(...array123);
Observable.of()
const observable = Observable.from({
[Symbol.observable](observer) {
// ...
}
});
Observable.from()
const observable2 = Observable.from([1, 2, 3]);
const observable = new Observable(observer => {
document.addEventListener('click', observer.next, true);
return () => {
document.removeEventListener(eventName, observer.next, true);
};
});
observable
.map(e => e.target)
.forEach(::console.log);
Exemple :
Écouter le DOM
Merci de votre attention
Et à vos claviers !
The future of Javascript
By ereold
The future of Javascript
Presentation about ES6 and the future of javascript today
- 2,153