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

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