Javascript : ce qu’il faut savoir

Publié par Vincent Crépin le lundi 22 avril 2013 à 16:07

Au cours de mes billets précédents (ici et ici), j’ai élaboré sur la plateforme HTML5 au cœur de laquelle se trouve Javascript. Ce billet se veut une description de certaines particularités de ce langage extrêmement puissant mais parsemé de pièges. Il faut mentionner que mon expérience personnelle est très axée sur l’utilisation de langages orientés objets fortement typés et c’est la raison pour laquelle Javascript m’est apparu au départ assez différent et inconfortable.

Javascript est LE langage du Web. Inventé en 1994 (en même temps que Java duquel il s’inspire pour sa syntaxe), il a depuis proliféré pour ainsi se retrouver au cœur de la grande majorité des sites web existants. Avec l’arrivée du standard HTML5, son avenir est assuré pour de nombreuses années à venir.

Javascript est un langage fonctionnel. Les fonctions sont ce qu’on appelle des  « first-class citizens » du langage. En effet, les fonctions sont des objets et peuvent ainsi être traitées comme tel : déclarées, passées en paramètres à d’autres fonctions et exécutées au moment opportun. Elles peuvent même être anonymes (ne pas posséder de nom) ce qui représente une force importante comme on le verra plus loin.

Je vais m’attarder à 3 particularités dans ce billet en faisant ressortir les forces et faiblesses du langage par rapport à ces particularités : la visibilité des fonctions (function scope), la visibilité des variables (variable scope) et les closures.

Visibilité des fonctions

Une fonction étant déclarée quelque part (directement dans le HTML ou dans un fichier .js importé) et possédant un nom comme dans l’exemple ci-dessous devient automatiquement globale à l’environnement d’exécution.

function double(x) {
    return x * x;
}

L’environnement global d’exécution correspond à la variable magique window qui est omniprésente. Donc la fonction précédente correspond à window.double. Cela veut dire qu’elle est accessible de partout et cela pourrait occasionner des collisions de noms si une autre fonction avec le même nom était déclarée à un autre endroit (par exemple dans une librairie que vous importez). Il existe une façon très simple de prévenir ces inconvénients et c’est d’utiliser la notion de namespace. En Javascript, il s’agit simplement de déclarer toutes nos fonctions applicatives en les attachant à un objet comme le montre l’exemple suivant :

var APP_NAMESPACE = [];

APP_NAMESPACE.double = function (x) {
     return x * x;
};

Ainsi, l’appel de cette fonction se fera désormais comme suit :

var resultat = APP_NAMESPACE.double(3);

Cette technique extrêmement simple peut éviter de nombreux pièges qui sont souvent difficiles à identifier et permet de mieux structurer le code. Par contre, elle n’est pas parfaite pour quelques raisons. Principalement, APP_NAMESPACE étant déclaré comme un ARRAY ([]), les méthodes de ARRAY sont disponibles même si on ne le souhaite pas dans ce cas. Par exemple, les appels suivant sont parfaitement valides :

APP_NAMESPACE.push(10);
alert(APP_NAMESPACE.pop());

Il existe des techniques plus avancées (avec des objets ou fonctions) qui permettent de résoudre ces problèmes. Voici un exemple avec un objet :

var APP_NAMESPACE = {

    au_carre: function (x) {
        return x * x;
    },
   
    racine_carre: function (x) {
        return Math.sqrt(x);
    },
   
    copier_nombre: function (x) {
        return this.racine_carre(this.au_carre(x));
    }
};

console.log(APP_NAMESPACE.au_carre(5));

Avant de terminer avec les fonctions, j’ajouterais une technique simple et importante. La fonction qui suit est appelée une fonction immédiate.

function avertir(x) {
    alert('Bonjour!');
}();

Remarquez les deux parenthèses à la fin de la définition. Celles-ci ont pour effet d’exécuter la fonction immédiatement après sa déclaration. Cette technique est très utile pour effectuer des tâches d’initialisation par exemple.

Visibilité des variables

Toute variable déclarée à l’extérieur d’une fonction devient automatiquement globale en Javascript. Même une variable utilisée dans une fonction mais non déclarée devient globale. L’exemple suivant illustre ceci :

function test(x) {
    y = 0;
    return x + y;

}

Dans cet exemple, la variable y est non déclarée (non précédée de var) et ainsi devient globale à l’environnement d’exécution. Vous imaginez les répercussions que cela peut avoir… L’exemple de code suivant illustre ceci :

y = 1;
function test(x) {
    y = 10;
    return x + y;
}
alert(y); // affiche 1
test(20);
alert(y); // affiche 10

C’est là une des plus grandes faiblesses du langage. Pour se prémunir contre cet effet potentiellement très néfaste, il est essentiel d’utiliser la notion de strict) partout dans le code. Cette notion préviendra plusieurs erreurs de ce genre en produisant une erreur lors de l’exécution. Voici comment l’utiliser :

function test(x) {
    'use strict';
    var y = 0;
    return x + y;
}

Une autre notion importante par rapport à la visibilité des variables (scope) est que contrairement à ce que l’on a l’habitude de voir, la visibilité est de type fonction et non block. L’exemple suivant illustre ce point :

function test(x) {
    'use strict';
    var y = 0;
    y += 1;
    for(var i = 0; i < 10; i +=1) {
         var z = 0;
         y = z + i;
    }
    return x + z; // z parfaitement valide même si déclaré dans le for<
}

On remarque que 3 variables sont définies dans cette fonction (y, i et z). Le scope de toutes ces variables est fonction. Cela revient exactement au même que l’exemple suivant :

function test(x) {
'use strict';
var y = 0,
i = 0,
z = 0;
y += 1;
for(i = 0; i < 10; i +=1) {
z = 0;
y = z + i;
}
return x + z;
}

On remarque que les variables sont déclarées au début de la fonction. On appelle ce phénomène « variable hoisting ». Pour éviter toute confusion, il est donc essentiel de prendre l’habitude de déclarer toutes les variables au début des fonctions.

Closure

Les closures représentent un concept extrêmement important et puissant du langage. En gros, on peut définir les closures comme suit: Une fonction hérite du contexte dans lequel elle est déclarée en plus de celui dans lequel elle est exécutée. Voici un exemple simpliste pour illustrer ce point :

function test(x) {     
    ‘use strict’;     
    var callback = function (y){         
        return x * y;     
    };
    return test2(callback); 
} 
function test2(callback) {    ‘use strict’;    var z = 2;     if (typeof callback === "function") {        return callback(z);     } } var result = test(3); // retourne 6

Cet exemple simple montre que la fonction callback est définie (de façon anonyme i.e. elle ne porte pas de nom) dans le contexte de test. Elle hérite donc de ce contexte (la valeur de la variable x). Elle est ensuite passée en paramètre à une autre fonction qui elle déclenche son exécution en lui passant y comme paramètre. Le résultat est 6 puisque la valeur de x obtenue lors de la déclaration est héritée. Cet exemple illustre la puissance de ce concept un peu difficile à saisir au début mais il est essentiel de le maîtriser pour plusieurs raisons mais surtout parce que beaucoup d’appels en Javascript (surtout au niveau des librairies) se font en asynchrone. Sans les closures, la seule façon de maintenir des états entre l’appel et le retour asynchrone serait d’utiliser des variables globales ce qui peut s’avérer très dommageable. Les closures représentent une façon très élégante de gérer ces cas.

Conclusion

Javascript est un langage incontournable plus que jamais pour les années à venir et il est essentiel de maîtriser ses subtilités pour en faire un usage efficace et éviter des erreurs très difficiles à identifier. Cet article a présenté certaines particularités importantes de celui-ci mais il ne s’agit que de la pointe de l’iceberg. Mon conseil est donc celui-ci : ne pas prendre pour acquis l’apparente simplicité de cette technologie et prendre le temps de se documenter et d’apprendre toutes les subtilités du langage. Aussi, dès le début d’un projet qui inclut Javascript, prendre l’habitude de valider tout le code avec JSLint. C’est un utilitaire en ligne qui valide la syntaxe du code et détecte une panoplie d’erreurs potentielles.

Références

Voici quelques références que je vous propose si vous voulez approfondir les différents sujets présentés dans ce billet:

Mots-Clés :
blog comments powered by Disqus

0 Comments:

Post a comment

Comments have been closed for this post.