JS FTW !

rappels, bonnes pratiques, conseils

par @borisschapira | Clever Age

Envie de changer de thème ?

Reveal.js en offre plusieurs:
Default - Sky - Beige - Simple - Serif - Night
Moon - Solarized

En savoir plus sur Reveal.js

JavaScript

Hier et aujourd'hui

Création

  • Inventé par Brendan Eich
  • Présenté pour la première fois en 1985, MochaScript (serveur)
  • Netscape récupère le projet et le renomme LiveScript
  • Sun s'associe à Netscape. Navigator 2, livré en 1995, inclus JavaScript
  • Microsoft met la pression avec JScript (1996), et pousse involontairement la standardisation ECMA

La Nouvelle Vague

  • Les années 2000 voient fleurir les frameworks JS (Prototype, Mootolls, jQuery...)
  • En 2009, Node.js redonne à JavaScript sa noblesse sur serveur
  • Fin des années 2000 : les potentiels successeurs se compilent en JavaScript (Coffee, Dart, TypeScript)

Aujourd'hui

  • Un des langages les plus populaires au monde
  • Dernière version : 1.8.5
  • Supporté par tous les navigateurs principaux : Navigateurs principaux : Chrome, Firefox, IE, Opera et Safari
  • Supporté par de nombreux périphériques "exotiques"

Briques applicatives JS

Les moteurs d’exécution JavaScript se séparent de plus en plus des navigateurs et deviennent des technologies stand-alone :

  • V8 : Chrome, Node.js
  • JägerMonkey : Firefox
  • SquirrelFish/Nitro : Safari
  • Chakra : IE9+, Windows 8.*

Demain

Du JavaScript embarqué dans de nombreux périphériques.

Exemple avec le microcontrôleur JavaScript de Tessel...

Le langage

Rappels de fonctionnement

JavaScript

  • Variables, fonctions, objets, instructions, opérateurs...
  • Sensible à la casse, délimiteurs;
  • Nommage des variables : des symbôles spécifiques sont tolérés, comme $ ou _. Utilisez-les à bon escient, par exemple pour distinguer vos variables jQuery ($variable) ou avoir une information de portée ( _internalVar).

Variables

Typage faible


function(){
    var _i = 10;
    typeof _i; // "number"

    _i = "dix";
    typeof _i; // "string"
}
                    

Portée et Hoisting


var testVar = "Variable globale";

function example() {
    alert(testVar); // undefined

    var testVar = "Variable locale";
    alert(testVar); // "Variable locale"
}

example();
                    

Variables

Déclarations


// Déclarations multiples
var maVariable1,
    maVariable2 = 15,
    maVariable3;

// Déclarations de variables égales
var maVariable1 = maVariable2 = 15;
                    

Référence vs. copie


var a = 3.14;   // Déclare et initialise une variable
var b = a;      // copie le contenu de a dans b
a = 4;          // modifie la valeur de a
alert(b);       // la copie n'a pas changé : 3.14

var a = [1,2];  // Initialise une variable avec un tableau
var b = a;      // copie la référence dans une nouvelle variable
a[0] = 99;      // modifie le tableau selon la référence
alert(b);       // affiche le tableau modifié : [99,2]
                    

Vrai ou faux ?

Opérateur d'évaluation : !!


var vrai = !!(0);       // faux
var faux = !!("ftw");   // vrai
                

Sont faux :


var falsies = [false, 0, "", null, undefined, NaN];
                

Tout le reste est vrai !

Vrai ou faux ?

Mais attention, certains faux se valent


(false == "") && (false == 0) && (0 == ""); // true
(null == null) && (null == undefined); // true
                

Mais les deux groupes ne font pas bon ménage...


(null == false) || (null == 0) || (null == ""); // false
(undefined == false) || (undefined == 0) || (undefined == ""); // false
                

Enfin NaN n'est égal à personne, pas même lui-même


(NaN == false) || (NaN == 0) || (NaN == "")
    || (NaN == undefined) || (NaN == null); // false
(NaN == NaN); // false
                

Tester une variable

Par rapport à undefined et null


if (undefined != monObjet) {
   // compare l'égalité de mon objet face à 'null' ou 'undefined'
}
                

Par rapport à son évaluation booléenne


if (monObjet) {
    // On entrera que si monObjet n'est pas considéré comme faux
    // donc différent de false, "", undefined, null ou NaN
}

// équivaut à
monObjet && // portion de code

// équivaut à
!!(monObjet) && // portion de code
                

Fonctions

Nommées


function sheldon() {
    alert("Bazinga!");
}
sheldon();
                    

Anonymes


var sheldon = function() {
    alert("Bazinga!");
}
sheldon();
                    

Fonctions : portée

Pour le test


function callIt(name) {
    try { window[name]() }
    catch (e) { console && console.log('Erreur durant l\'appel de '+ name); }
}
                    

Test de la portée des fonctions


callIt("a");    // Ok
callIt("b");    // Erreur durant l'appel de b
callIt("c");    // Erreur durant l'appel de c

function a() {}

var b = function () {};
callIt("b");    // Ok

var c = function d() {};

callIt("c");    // Ok
callIt("d");    // Erreur durant l'appel de d
// En réalité, d n'est défini que durant la création de c
                    

Moralité : attention aux fonctions anonymes !

Objets et tableaux

Object : ensemble de valeurs nommées


var obj = new Object();
obj.x = 1;
obj["y"] = 2;

var obj = { x : 1, y : 3 };

                    

Tableaux : ensemble de valeurs indexées


var arr = new Array(1.2, true, { x:1, y:3 });

var arr = [1.2, true, { x:1, y:3 }];
                    

Objets et tableaux

Parcourir avec for ... in


function consoleContenu(element) {
    var result = [];
    for( var identifiant in element ) {
        result.push(identifiant + ":" + element[identifiant]);
    }
    console && console.log(result.join(', '));
}

consoleContenu(arr);    // 0:1.2, 1:true, 2:[object Object]
consoleContenu(obj);    // x:1, y:3
                

Au futur... les Promises

Problème : parfois on arrive après la bataille


var img = document.querySelector('.img-1');
img.addEventListener('load', function() {
    // l'image est chargée !
});
                

On n'obtiendra pas le résultat escompté si on arrive après le chargement de l'image...

Au futur... les Promises

On peut essayer de corriger ça avec complete...


var img = document.querySelector('.img-1');

function loaded() {
    // l'image est chargée !
}

if (img.complete) {
    loaded();
}
else {
    img.addEventListener('load', loaded);
}
                

... mais on n'a aucun équivalent pour l'erreur, par exemple
Zoidberg is ashamed and trash himself

Au futur... les Promises

Les Promises arrivent via des librairies, en attendant leur plein support. Dans l'idée, ça donne ça :


var promise = new Promise(function(resolve, reject) {

    // faire un truc, peut-être asynchrone, puis...

    if (/* tout a bien marché */) {
        resolve("Ces trucs ont marché !");
    } else {
        reject(Error("Ça a foiré"));
    }
});

promise.then(function(result) {
    console.log(result); // "Ces trucs ont marché !"
}, function(err) {
    console.log(err); // Error: "Ça a foiré"
});
                

Cool, non ?

Au futur... les Promises

Envie d'en savoir plus ?

Brad Pitt has an instant of clarity : Shit Yeah !

Allez lire cet excellent article de Jake Archibald traduit par Christophe Porteneuve

JavaScript permet-il la POO ?

Tout dépend de la définition qu'on a de la POO...

POO "Classique"

La POO repose sur 5 fondamentaux définis entre 1960 et 1970

  1. L’objet
  2. L’encapsulation
  3. Les messages
  4. Le typage
  5. Le polymorphisme

Prérequis #1 : l'objet

On peut utiliser Object


var myHero = {
    realName: "Clark Kent",
    surname: "Superman" 
}
                

Suivant !

Prérequis #2 : l'encapsulation

Là, ça ne colle plus... Je peux modifier toutes les infos librement


var myHero = {
    realName: "Clark Kent",
    surname: "Superman" 
}
myHero.realName = "Lex Luthor"; // Mouhahahahaaaa !
                

Mais on sait comment rendre indisponible une variable "à l'extérieur" : il suffit de la mettre dans une fonction


function Hero(){
    var realName = "Clark Kent",
        surname = "Superman";
}
var myHero = new Hero();
                

Les champs sont cachés et on peut se servir de Hero comme constructeur paramétré si on le souhaite

Prérequis #3 : accès extérieurs

On peut ajouter un accesseur


function Hero(name, surname){
    var realName = name,
        surname = surname;
    this.getSurname = function(){
        return surname
    };
}

var myHero = new Hero("Clark Kent", "Superman");
alert(myHero.getSurname()); // "Superman"
alert(myHero.realName); // undefined
                

Prérequis #4 : le typage

A chaud, pas trop possible, car JavaScript n’a pas de types évolués : à part les types primitifs, tout est Object


alert(typeof(myHero)); // object
                

Par contre si je maintiens la liste des types existants, je peux tous les tester et arriver à la conclusion que :


alert(myHero instanceof Hero); // « true »
                

Prérequis #5 : le polymorphisme

JavaScript n’enregistre, pour un objet, qu’une seule référence vers une « source » structurelle : son prototype.

Pas de polymorphisme donc, mais on peut pratiquer l'héritage (ce qui, pour certaines personnes, est la règle #5 de la POO). Donc finalement, c'est cool :)

Programmation Orientée Prototype

En utilisant le prototype, on peut optimiser l'exemple précédent pour ne pas recopier l'accesseur dans toutes les instances et le définir uniquement quand Hero n'est pas encore initialisé :


function Hero(name, surname){
    var realName = name,
        surname = surname;
    if (typeof Hero.initialized == "undefined"){
        Hero.prototype.getSurname = function(){
            return surname
        };
    }
}

var myHero = new Hero("Clark Kent", "Superman");
alert(myHero.getSurname()); // "Superman"
alert(myHero.realName); // undefined
                

Programmation Orientée Prototype

On peut même réorganiser le code autour des notions de privé et public :


function Hero(name, surname){
    var publics = this,
        privates = {};
    publics.name = name;
    privates.surname = surname;
    if (typeof Hero.initialized == "undefined"){
        Hero.prototype.getSurname = function(){
            return privates.surname
        };
    }
}

var myHero = new Hero("Clark Kent", "Superman");
alert(myHero.getSurname()); // "Superman"
alert(myHero.realName); // undefined
                

Programmation Orientée Prototype

Et ajouter la notion de constructeur, de méthodes statiques et de chaînage des propriétés


/*** Déclaration du type ***/
function Hero(name, surname) {
    this.init(name, surname);
    Hero.total = typeof Hero.total != 'undefined' ? Hero.total + 1 : 1
}
/*** Enrichissement ***/
(function () {
    Hero.total = 0;

    /*** Constructeur ***/
    Hero.prototype.init = function (name, surname) {
        this.__privates = this.__privates || { surname: surname };
        name && this.name(name)
    };

    /*** Getter/Setter ***/
    Hero.prototype.name = function (value) {
        var privates = this.__privates;
        if (typeof value == 'undefined') {
            return privates.name
        } else {
            privates.name = new String(value).toString();
            return this
        }
    };

    /*** Getter ***/
    Hero.prototype.surname = function () {
        var privates = this.__privates;
        return privates.surname
    }
}());

// Utilisation
var Super = new Hero('Clark Kent', 'Superman'),
    Spidey = new Hero('Peter Parker', 'Spiderman');

// Variable statique
alert(Hero.total);

// Chaînage
alert(Spidey.name('Otto Octavius').surname());
alert(Spidey.name())
                

Programmation Orientée Prototype

Mais attention alors à la dépendance au prototype


var myHero = new Hero("Clark Kent", "Superman");
alert(myHero.surname()); // "Superman"

Hero.prototype.surname = function(){ return "Superslip" };
alert(myHero.surname()); // "Superslip"
                

Encore un coup de Lex Luthor

Pièges et Astuces

Piège : contexte

this représente le contexte dans lequel s’exécute une fonction au moment où elle est appelée


function quiSuisJe() {
    alert(this);
}

var coucou = {
    quiSuisJe : function () {
        alert(this);
    }
};

quiSuisJe();        // [object Window]
coucou.quiSuisJe(); // [object Object]
                

Piège : gestion des erreurs

Les gestion des erreurs est possible en JavaScript


try {
    // code JavaScript à surveiller
}
catch (erreur) {
    alert(erreur.name);
    alert(erreur.message);
}
finally {
    // code JavaScript à exécuter dans
    // tous les cas (facultatif)
}
                

Malheureusement, cela entraine une surcharge mémoire indédiable sur V8, aujourd'hui moteur leader.

Piège : nombres

Maximum, minimum


var x = 9007199254740992; // Max
var y = -x;
x == x + 1; // true
y == y - 1; // true

z = x + 2; // 9007199254740994, ok
z - 1; // 9007199254740992, WTF ?
                

Utilisez vos opérateurs arithmétiques avec en tête le 64bits mais vos décalages et opérations bit à bit en 32 bits seulement (car vous ne savez pas où est la virgule flottante)


var x = 9007199254740992; // 2^53, [10000000....00]
x / 2;      // 4503599627370496, ok
x >> 1;     // 0 au lieu de 2^52
x | 1;      // 1 au lieu de 2^52 + 1
                

Pour en savoir plus sur les nombres, foncez-ici.

Piège : Nombres

NaN est une valeur invalide de la notation à virgule flottante IEEE 754 qui n'est donc égale à rien, même pas à elle-même.


Math.sqrt(-2) != Math.log(-1) // true
Math.sqrt(-2) != Math.sqrt(-2) // true
                

isNaN() vous permettra cependant de tester un NaN, mais elle est imparfaite. Préférez-lui Number.isNaN() quand c'est possible.


isNaN(NaN) === Number.isNaN(NaN) === true; // Hum, ok !
Number.isNaN('NaN is not a number'); // false, ok
isNaN('NaN is not a number'); // true... WTF ?!
                

Piège : Référence Vs. Type


var objects = [{}, {}, {}];

for(var i = 0; i < objects.length; i++) {
    objects[i].logIndex = function() {
        console.log(i);
    }
}

objects[0].logIndex(); // 3, WTF !
objects[1].logIndex(); // 3, WTF !
objects[2].logIndex(); // 3, WTF !
                

Quand i est passé au console.log(), c'est par référence... Pour corriger, voyons d'abord deux astuces !

Astuce : renvoi de fonction

Test réalisé à chaque fois


function TestURL(){
    if (window.location == "http://google.fr"){
        alert("Google !");
    } else {
        alert("Pas Google");
    }
}
                

Test réalisé une fois


function TestURL(){
    if (window.location == "http://google.fr"){
        return function() {
            alert("Google !");
        }
    } else {
        return function() {
            alert("Pas Google");
        }
    }
}
                

Astuce : IIFE ("iffy")

Fonctions anonymes immédiatement appelées, un pattern de développement courant en JS. Permet d'éviter le Hoisting et de créer des portées lexicales (namespaces).


(function(){
    // du code
})();
                

Autre intérêt des IIFE, s'assurer que certain opérateurs sont surchargés comme vous le souhaitez. Exemple, si vous n'êtes pas sûr que $ correspond à jQuery, encadrez votre code de :


(function($){
    // votre code
})(jQuery);
                

Piège : Référence Vs. Type

Utilisation du renvoi de fonction et d'une IIFE


var objects = [{}, {}, {}];

for(var i = 0; i < objects.length; i++) {
    (function(index){
        objects[i].logIndex = function() {
            console.log(index);
        }
    })(i)
}

objects[0].logIndex(); // 0!
objects[1].logIndex(); // 1!
objects[2].logIndex(); // 2!
                

Quand index est passé au console.log(), c'est par valeur cette fois !

Piège : DOM, JS et Memory Leak

JavaScript et DOM n'ont pas le même Garbage Collector.

Bien que tous deux capables de gérer leurs références circulaires, ils ne sont pas capables de gérer les dépendances de l'autre univers...


var jsObject;
function FuiteMemoire(idDiv){
    // On stocke dans une variable JS une référence au DOM
    jsObject = document.getElementById(idDiv);

    /* Plein de code */

    //  Et là, le développeur se rate et référence l’objet JS
    // depuis le DOM
    document.getElementById(idDiv).expandoProperty= jsObject;
}

// Epic Fail : Memory Leak
                

Piège : DOM, JS et Memory Leak

Variante : le développeur référence depuis l'intérieur d'une énorme méthode un élément du DOM, qui lui même référence l'objet.


function Encapsule(element){
    // Assignation d’une propriété
    this.elementReference = element;

    // Référence circulaire
    element.expandoProperty = this;
}

function EnormeMethode() {
    /* Plein de code */

    Encapsule(document.getElementById("maDiv"))

    /* Encore plein de code */
}
                

EnormeMethode ne pourra jamais être nettoyée.

Piège : DOM, JS et Memory Leak

Variante la plus courante : attachement d'un évènement à une méthode anonyme, empêchant le GC de nettoyer l'un ou l'autre si l'un des éléments est supprimé.


window.addEventListener("load", function(){
    // obj sera ramassé par le GC dès qu’il sera sorti du contexte
    var obj = document.getElementById("element");

    // en revanche, la liaison ici relie un objet DOM à une
    // méthode anonyme du JS, définie dans le même contexte que obj
    obj.addEventListener("click", function(evt){
        // logique
    }, false);
}, false);
                

Présent dans 99% des scripts.

Piège : DOM, JS et Memory Leak

Pour éviter ces problèmes, utilisez removeEventListener ou off (jQuery).


var item = document.createElement("div");
var listener = function(event) {
    // du code
    // on enlève le listener pour éviter la fuite
    this.removeEventListener("click", listener, false);
};
item.addEventListener("click", listener, false);
                

Frameworks

et librairies

(liste non-exhaustive)

jQuery

Framework rapide, compact et gérant très bien l'asynchrone de John Resig, ayant remporté un grand succès, principalement parce qu'il a été adopté par les intégrateurs HTML.

Souvent utilisé pour faire des choses simples. Au lieu de les faire simplement.


// jQuery
$(el).hide();
$(el).show();   // Ajoute un "display:block", WTF ?!

// IE8+
el.style.display = 'none';
el.style.display = '';      // Ne fait que ce que vous lui demandez
                

You Might Not Need jQuery

Autres Frameworks généraux

  • ZeptoJS : framework très minimaliste ayant la particularité de proposer un sous ensemble de l'API jQuery. Peut habilement remplacer ce dernier, mais attention à la compatibilité navigateur.
  • Underscore.js : framework utilitaire orienté manipulation de données d'inspiration Ruby
  • Lo-Dash : framework utilitaire orienté consistence et performance

Frameworks de composants

  • jQuery UI : librairie de composants associée à jQuery
  • YUI : librairie de composants de Yahoo!
  • Dojo Toolkit : librairie de composants dont le développement est sponsorisé par IBM et SitePen

Frameworks pour WebApps full-client

Polyfills utiles

Ces librairies sont trop génériques ?

Jetez un oeil à MicroJS et ses micro-librairies (inférieures à 5Ko) rendant chacune un service très précis.

MicroJS est sur GitHub et n'importe qui peut y ajouter son script licencié MIT ou équivalent.

Exemple : preloadr.js

Quelques outils bien pratiques

pour mieux faire du JavaScript

Partager un snippet

  • JsFiddle et CodePen : vous permettent de partager rapidement un exemple Web (HTML+JS+CSS)

Qualité

  • JSLint : outil d'audit de code de Douglas Crockford (Yahoo, inventeur de JSON et auteur de JavaScript: The Good Parts. JSLint est utile, bien que très tatillon, voire intransigeant.
  • JSHint : fork JSLint de Anton Kovalyov, pour développer un outil plus proche de la communauté.
  • JS Manners : pour évaluer un script fourni par un tiers.
“JSLint can suck it”, Brendan Eich

Compilateurs

  • YUI Compressor : compilateur/compresseur de Yahoo, bien éprouvé, hors-ligne (Java)
  • Closure Compiler : compilateur de Google, en ligne, en API ou hors ligne (Java)

Une question de performance ou de qualité

  • JSPerf.com : plate-forme de crowdtesting de code(s) JavaScript.
  • FastDom : une librairie optimisant les lectures/écritures dans le DOM.
  • Immutable : une librairie pour la manipulation de structures de données persistentes.

Programmer en JS, mais pas tout à fait

  • TypeScript : sur-ensemble de JavaScript, offrant des fonctionnalités d'ECMAScript 6 (classes, typages) et compilant en JavaScript d'aujourd'hui. Par Anders Hejlsberg (concepteur du Framework .NET).
  • CoffeeScript : langage de développement basé sur l'indentation, se compilant en JavaScript et introduisant notamment des raccourcis de code comme @ pour this et :: pour les méthodes de prototype.
  • Dart : langage de développement Web productif, compilant en JavaScript, livré avec son éditeur. Par Google.

Les portes ouvertes par JavaScript

  • Node : le retour de JavaScript côté serveur, pour des applications vraiment scalables.
  • Casper : tests fonctionnels sur Gecko (via Slimmer) et Webkit(via Phantom).
  • WinJS : pour développer des applications Windows 8 sous Chakra

FIN

ça vous a plu ?

par @borisschapira | Clever Age

Bonus : WTF JS ?!

Sources

Bonus : WTF JS ?!


var d = new Date(Date.UTC(2014, 10, 26, 13, 31, 56)); 
// Wed Nov 26 2014 14:31:56 GMT+0100 (Paris, Madrid)
                

d.getYear() // => 114
d.getMonth() // => 10
d.getDay() // => 3
                

Bonus : WTF JS ?!


var d = new Date(1845, 3, 17); 
// Thu Apr 17 1845 00:00:00 GMT+0200 (Paris, Madrid (heure d’été))
                

var d2 = new Date(2014, -2163, 4217); // Gni ?
// Thu Apr 17 1845 00:00:00 GMT+0200 (Paris, Madrid (heure d’été))
                

Bonus : WTF JS ?!


result = [] + [];
                

result = [] + []; // ""
                

Bonus : WTF JS ?!


result = {} + [];
                

result = {} + []; // [object Object]
                

Bonus : WTF JS ?!


result = '4' - '2';
result = '4' + 2;
result = '4' + + '2';
result = '4' + - '2';
result = '4' + - + - - - + + + + - + - - '-2';
                

result = '4' - '2'; // 2
result = '4' + 2; // "42"
result = '4' + + '2'; // "42"
result = '4' + - '2'; // "4-2"
result = '4' + - + - - - + + + + - + - - '-2'; // "42"
                

Bonus : WTF JS ?!


var x = 42;
result1 = '42' - x + x;
result2 = '42' + x - x;
                

var x = 42;
result1 = '42' - x + x; // 42
result2 = '42' + x - x; // 4200
                

Bonus : WTF JS ?!


result = 'ro' + + 'ro' + ' the accuser';
                

result = 'ro' + + 'ro' + ' the accuser'; // "roNaN the accuser"
                

Bonus : WTF JS ?!


"wtf" - 1
                

"wtf" - 1 // NaN
                

Bonus : WTF JS ?!


result = Array(16).join("wtf" - 1) + ' Batman !';
                

result = Array(16).join("wtf" - 1) + ' Batman !';
// "NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN Batman !"