Logo JavaScript

Top 13 des erreurs commises avec Javascript, par les développeurs de Tuenti, traduction et critique

Flattr this!

Je suis tombé il y a quelques temps sur ce lien : http://blog.tuenti.com/dev/top-13-javascript-mistakes/. Ce billet présente les 13 erreurs les plus communes (d'après l'auteur) des développeurs Javascript. Tuenti est un réseau social espagnol basé à Madrid, l'inscription n'est possible que sur invitation. Ce site est l'un des plus visités en Espagne d'après Alexa Internet, une filiale d'Amazon spécialisée dans la publication de statistiques à propos de l'internet mondial. Voilà une version traduite de l'article et mon point de vue au fur et à mesure :

Erreur #1 : Utilisation de for...in pour parcourir les tableaux

Exemple :

var myArray = [ 'a', 'b', 'c' ];
for (var prop in myArray ) {
    console.log(myArray[prop]);
}

Idée avancée : Le problème principal est que l'utilisation de for...in ne garantit pas l'exploitation dans l'ordre des données du tableau. Ceci signifie que vous aurez un résultat différent à chaque exécution (ou presque). De plus si quelqu'un (quelque chose plutôt) modifie le prototype de l'objet Array pour ajouter des méthodes personnalisées, votre script va parcourir ces méthodes et risque fortement de tout simplement crasher.

Solution proposée : toujours utiliser une boucle for classique pour parcourir les tableaux.

var myArray = [ “a”, “b”, “c” ];
for(var i = 0; i < myArray.length; i++) {
    console.log(myArray[i]);
}

Critique personnelle : Deux suggestions de ma part pour celle-ci. Je suis sceptique quand aux problèmes d'ordonnancement du tableau, d'autant que la façon de parcourir le tableau dépend de l'implémentation faite par chaque éditeur de navigateur. Il est vrai qu'au moins avec une boucle for classique, on est sûrs que ça sera fait dans l'ordre. Mais, honnêtement, je ne confierai JAMAIS un traitement basé sur un ordre précis à une exécution réalisée sur le navigateur du client. Ne serait-ce que parce qu'un tel traitement est qualifiable de majeur voire critique. Sachant qu'un utilisateur peut altérer sans difficulté l'exécution d'un script (pensez Firebug, Webkit Inspector, Developer Tools, ...), la sécurité de vos données serait donc plus que compromise.

De plus, l'idée qu'on puisse modifier le prototype de Array est pour moi une aberration sans nom. C'est exactement comme si vous décidiez de modifier n'importe quel type natif dans n'importe quel langage pour une raison x ou y. Vous ne feriez bien évidemment jamais ça parce que vous aurez le réflexe de tout simplement encapsuler ou hériter le type à modifier. Et pourquoi pas en javascript?!

Erreur #2 : Dimensionnement de tableaux

Idée avancée : Définir un tableau comme ceci

var myArray = new Array(10);

Solution proposée : 2 problèmes sont identifiables ici. D'abord le développeur cherche à créer un tableau 10 emplacements qui seront vides. Le souci est que si vous interrogez myArray[2] par exemple, vous obtiendrez "undefined". Inefficace donc si vous vous attendiez à null. Vous ne réservez pas la mémoire. Donc pas besoin de définir de taille du tableau. L'autre problème est qu'utiliser le constructeur de Array est plus long que la notation littérale. Mieux vaut donc faire ceci :

var myArray = [];

Critique personnelle : Rien de spécial hormis que faire la différence entre Array() et [] est devenue inutile tellement les navigateurs modernes ont vu leurs performances s'améliorer. Ceci dit, parce que je sens l'ami mageekguy s’esclaffer que moi je tienne ce propos alors que je suis le premier à chipoter en PHP, n'oublions pas que tout ce qu'on peut gagner aussi facilement est à prendre. D'autat plus qu'en faisant ainsi votre fichier de script est plus léger et vous avez codé moins de caractères.

Erreur #3 : Propriétés non définies

Exemple :

var myObject = {
    someProperty: “value”,
    someOtherProperty: undefined
}

Idée avancée : Les propriétés non définies n'existent tout simplement pas. Essayez de parcourir votre tableau à la recherche de someOtherProeperty, vous ne trouverez pas cet élément.

Solution proposée : Si vous voulez définir des propriétés encore non-initialisées, donnez leur la valeur null.

var myObject = {
    someProperty: “value”,
    someOtherProperty: null
}

Erreur #4 : Mauvais usage des fermetures

Exemple :

function(a, b, c) {
    var d = 10;
    var element = document.getElementById(‘myID’);
    element.onclick = (function(a, b, c, d) {
        return function() {
            alert (a + b + c + d);
        }
    })(a, b, c, d);
}

Idée avancée : ici le développeur a créé une première fonction avec 3 paramètres qui en appelle une autre au besoin avec un quatrième paramètre.

Solution proposée : Cette imbrication n'est pas nécessaire. En effet, faisant partie du corps de la première fonction, le corps de la seconde a connaissance des variables a,b et c. Pas besoin donc de les passer en paramètres. Idem pour d qui est instancié au cours de l'exécution de la première fonction.

function (a, b, c) {
    var d = 10;
    var element = document.getElementById(‘myID’);
    element.onclick = function() {
        alert (a + b + c + d);
    };
}

Critique personnelle : en soit le problème soulevé est fréquent et la solution suggérée exacte. Mais je ne défends pas l'exemple qui lui par contre suggère de mauvaises pratiques. Ajouter une méthode à exécuter sur un évènement doit se faire via un gestionnaire d'évènements pas en balançant comme ça à l'improviste une fonction anonyme. Et encore moins en comptant sur la portée naturelle des variables, sujet abordé dans l'erreur suivante.

Erreur #5 : Utilisation en boucle des fermetures

Exemple :

var elements = document.getElementByTagName(‘div’);
for (var i = 0; i < elements.length; i++) {
    elements[i].onclick = function() {
        alert(“Div number “ + i);
    }
}

Idée avancée : Ici, alors que le développeur voulait certainement affichier "Div number 0", "Div number 1", "Div number 2", ..., "Div number 9". En admettant, qu'il voulait s'arrêter à 10 éléments.

Le problème ici, c'est que les gestionnaires d’évènements vont ajouter une alerte sur chaque élément ciblé affichant le message "Div number " + i. Sans avoir encore calculer i.

Du coup, à chaque clic, vous aurez "Div number 9", du simple fait que i est accessible dans la fonction anonyme par la boucle.

Solution proposée : Passer par une fonction intermédiaire supplémentaire

var elements = document.getElementsByTagName(‘div’);
for (var i = 0; i<elements.length; i++) {
    elements[i].onclick = (function(idx) { //Outer function
        return function() { //Inner function
            alert(“Div number “ + idx);
        }
    })(i);
}

Critique personnelle : Mouais, j'aime pas cette façon de faire. C'est sale d'encapsuler une fonction anonyme dans une fonction déjà anonyme. A mon sens. Pour moi le plus simple et le plus propre, c'est de ne pas faire confiance aux paramètres et donc non seulement de les copier mais en plus de les contrôler avant usage. Et honnêtement, copier quelques variables dans la mémoire de votre navigateur, quand vous étiez sur IE 6, ça pouvait poser une gêne. Maintenant ça n'a plus aucun impact.

Erreur #6 : fuite mémoire avec les objets du DOM

Exemple :

function attachEvents() {
    var element = document.getElementById(‘myID’);
    element.onclick = function() {
        alert(“Element clicked”);
    }
};
attachEvents();

Idée avancée : Ce code créé des références en boucle. Mais les navigateurs n'ont pas toujours la capacité de nettoyer correctement derrière un tel code. Car ils conservent une référence sur chaque élément du DOM. Même après usage.

Solution proposée : supprimer à la place du navigateur cette référence.

function attachEvents() {
    var element = document.getElementById(‘myID’);
    element.onclick = function() {
        //Remove element, so function can be collected by GC
        delete element;
        alert(“Element clicked”);
    }
};
attachEvents();

Erreur #7 : Différencier les flottants des entiers

Exemple :

var myNumber = 3.5;
var myResult = 3.5 + 1.0; //We use .0 to keep the result as float

Idée avancée : En Javascript, il n'y a pas de différence entre les flottants et les entiers. Actuellement, tous les nombres sont représentés en utilisant une double précision 64 bits (IEEE 754).

Solution proposée : ne pas convertir les entiers en flottants.

var myNumber = 3.5;
var myResult = 3.5 + 1;

Critique personnelle : Rien à redire. Même si en soit, ce n'est pas une erreur mais il est toujours bon de rappeler les fondamentaux d'un langage.

Erreur #8 : Utilisation de with() comme d'un raccourci

Exemple :

team.attackers.myWarrior = { attack: 1, speed: 3, magic: 5};
with (team.attackers.myWarrior){
    console.log ( “Your warrior power is ” + (attack * speed));
}

Idée avancée : Avant de parler de with(), observons d'abord comment fonctionne le contexte de Javascript. Chaque fonction Javascript dispose d'un contexte qui conserve les variables auxquelles elle a accès. Soit les paramètres de la fonction et les variables définies en son sein. Ainsi qu'un lien vers le contexte parent. Lorsqu'une fonction souhaite accéder à une variable, le moteur cherche d'abord dans le contexte de cette fonction puis dans le contexte du parent et ainsi de suite jusqu'à la trouver ou retourner une erreur. C'est grâce à ce principe que marche les fermetures d'ailleurs.

Ce que with() fait, c'est envoyer un objet dans la chaîne de contextes. L'objet est inséré entre le contexte courant et le contexte parent. Du coup, le moteur cherche d'abord dans le contexte courant, puis dans l'objet injecté puis dans le contexte parent. Ce qui a des conséquences sur les performances puisque cela rend la fonction with() très lente. L'utiliser pour établir des raccourcis pour faire comme l'exemple ci-dessus est une mauvaise pratique.

Solution proposée : Ne pas utiliser la fonction with() pour faire des raccourcis, mais seulement quand nécessaire.

team.attackers.myWarrior = { attack: 1, speed: 3, magic: 5};
var sc = team.attackers.myWarrior;
console.log(“Your warrior power is ” + (sc.attack * sc.speed));

Erreur #9 : Utilisation de chaînes avec setTimeout()/setInterval()

Exemple :

function log1() { console.log(document.location); }
function log2(arg) { console.log(arg); }
var myValue = “test”;
setTimeout(“log1()”, 100);
setTimeout(“log2(” + myValue + “)”, 200);

Idée avancée : setTimeout et setInterval peuvent toutes deux recevoir une fonction ou une chaîne comme premier paramètre. Si vous passez une chaîne, le moteur devra utiliser un constructeur de Function, ce qui rallonge d'autant le temps de construction, surtout que ce constructeur n'est pas des plus rapides dans certains navigateurs. Et c'est moins clair que de passer directement une fonction.

Solution proposée : ne jamais passer de chaîne en paramètre à ces deux fonctions

function log1() { console.log(document.location); }
function log2(arg) { console.log(arg); }
var myValue = “test”;
setTimeout(log1, 100); //Reference to a function
setTimeout(function(){ //Get arg value using closures
    log2(arg);
}, 200);

Erreur #10 : Utilisation de setInterval() pour les grosses fonctions

Exemple :

function domOperations() {
    //Heavy DOM operations, takes about 300ms
}
setInterval(domOperations, 200);

Idée avancée : Il peut se produire un problème lorsqu'on utilise une fonction qui met plus de temps pour se dérouler que l'intervalle qu'on a prévu pour elle.

setInterval() planifie l'exécution d'une fonction seulement si il n'y a plus rien à exécuter dans la pile d'exécution principale. Et le moteur Javascript n'ajoute à la pile que si il n'y a pas déjà une autre exécution dans la pile. En gros, vous risquez d'avoir des exécutions décalées de votre fonction. setInterval() ne tient pas compte du temps qu'il faut pour résoudre l'exécution de la fonction, il ne fait que la planifier.

Solution proposée : préférer setTmeout() à setInterval()

function domOperations() {
    //Heavy DOM operations, takes about 300ms
    //After all the job is done, set another timeout for 200 ms
    setTimeout(domOperations, 200);
}
setTimeout(domOperations, 200);

Erreur #11 : Mauvais usage de this

Idée avancée : Pas d'exemple pour le coup parce qu'il est difficile d'illustrer ce cas. La valeur de this en javascript est différente de sa valeur dans d'autres langages.

Tout d'abord, la valeur de this à l'intérieur d'une fonction n'est définie que lorsque la fonction est appelée. Pas quand elle est déclarée. La valeur de this dépend donc de la façon dont est appelée la fonction. Voyons quelques exemples :

myFunction(‘arg1’);

Ici, this pointera sur l'objet global qu'est window dans tous les navigateurs.

Alors qu'avec :

someObject.myFunction(‘arg1’);

Là this pointe sur someObject.

var something = new myFunction(‘arg1’);

Ce coup ci, this pointera sur un objet vide.

En utilisant call() ou apply()

myFunction.call(someObject, ‘arg1’);

Et là this correspond à l'objet passé en premier paramètre.

Solution proposée : Vous ne pouvez pas avoir une fonction utilisant tout le temps de la même façon this.

Critique personnelle : là pour le coup, il est important de souligner que de toute façon, c'est une mauvaise pratique d'utiliser this tel quel. Il est nécessaire de faire un minimum de contrôles.

Erreur #12 : Utilisation de eval() pour accéder à  des propriétés dynamiques

Exemple :

var myObject = { p1: 1, p2: 2, p3: 3};
var i = 2;
var myResult = eval(‘myObject.p’+i);

Idée avancée : L'utilisation de eval() est très lente puisque nécessite la création d'un nouveau contexte puis sa destruction.

Solution proposée : il est plus simple d'utiliser la notation entre crochets

var myObject = { p1: 1, p2: 2, p3: 3};
var i = 2;
var myResult = myObject[“p”+i];

Erreur #13 : Utilisation de undefined comme d'une variable

Exemple :

if ( myVar === undefined ) {
    //Do something
}

Idée avancée : ce contrôle fonctionne en théorie mais cela relève de la chance. Tous les moteurs Javascript créent par défaut une variable nommée "undefined" ayant justement pour valeur undefined. Du coup, ici myVar étant undefined, le test répond vrai. Mais attention, c'est variable n'est pas en lecture seule, n'importe quel script peut modifier la valeur de la variable nommée undefined. Ceci dit, il serait vraiment étrange de trouver un script qui modifie la valeur de undefined. Mais pourquoi en prendre le risque?

Solution proposée : utiliser typeof pour vérifier si une variable est undefined

if ( typeof myVar === “undefined” ) {
    //Do something
}

Flattr this!

A propos de Mathieu

Ingénieur développeur web dans la vente par correspondance B2B, adepte de nouvelles technologies et d'innovation. Vous pouvez aussi me retrouver sur Twitter @mathrobin
Cette entrée a été publiée dans JavaScript, avec comme mot(s)-clef(s) , , , , , , , , . Vous pouvez la mettre en favoris avec ce permalien.
  • http://www.creatiq.fr/ JEi

    Comme d’hab merci pour ton partage et ton temps précieux !

    • http://www.mathieurobin.com/ Mathieu

      Merci 😀

  • Syndrael

    #Erreur 1: Même si surcharger Array() est à mon sens une abhérration au risque de perturber le développeur, c’est hélas une réalité de pas mal de solutions et frameworks.. Et il faut chercher pour trouver l’erreur.LOL !! Expérience et nuits blanches..
    S.

    • http://www.mathieurobin.com/ Mathieu

      On est d’accord, ce n’est pas sain de bosser comme ça. C’est peut-être même sûrement à cause de ce « détail » qu’ils l’ont mis en première erreur. Elle est moche, courante et galère à traquer.

      • Xavier

        bonjour,

        J’ai des erreurs à la pelle de style :
        java.lang.NumberFormatException: For input string: « 2.js »

        Ce n’est pas moi le développeur, et lui me dit que ce n’est pas grave, vous savez ce que c’est ?

        merci d’avance

        • http://www.mathieurobin.com/ Mathieu

          Ça ressemble à des erreurs Java et non JavaScript. A ne pas confondre, ce ne sont pas les mêmes langages. Je t’incite à aller poser ce genre de questions sur stackoverflow, où tu auras plein de réponses et où ta question servira de retour d’expérience ensuite à d’autres personnes.

  • xavier

    Bonjour,
    dans les fuites de memoire, je n’ai pas de methode delete ?
    ex: comment je detruit $content apres la ligne 7 ?
    6 var $content = $menu_load.hide();
    7 $(« .toggle »).on(« click », function(e){
    8 $(this).toggleClass(« expanded »);
    9 $content.slideToggle();
    10 });
    11
    Merci

    • http://www.mathieurobin.com/ Mathieu

      Soit tu es dans le corps d’une fonction, dans ce cas, à la fin de la fonction, $content sera détruit par le garbage collector. Soit tu es dans le scope principal, ta variable est donc globale. Elle ne sera alors jamais détruite tant que tu ne changes pas de page physique. C’est aussi une des raisons qui font que les variables globales ne sont pas recommandées.

  • Légo

    Pas mal ! Merci à toi !

    – Pour le #3, il m’arrive d’initialiser des variables à undefined dans le but de rendre le code plus lisible :

    var myObject = {
    someProperty: “value”,
    someOtherProperty: undefined
    }

    Cela indique à tous les développeurs que cet objet peut avoir un attribut someOtherProperty.

    – Pour le #13, le coup de la chance n’est pas le plus gros risque d’après moi. L’exception « ReferenceError » est un bien plus gros danger !

    EDIT: Je viens de voir la date du post… Désolé pour la nécromancie ^^

  • http://humblerumble.org Fabien Auréjac

    Sympa, merci pour le partage! Il est possible d’ajouter aussi que de nombreux développeurs oublient certains usages au niveau des nombres, comme:
    le transtypage +machaine est moins efficace que parseFloat(maChaine)
    ex:
    console.log(+ »3.14more non-digit characters »); // NaN
    console.log(parseFloat(« 3.14more non-digit characters »)); //3.14

Articles liés