Quick Tip – Optimisation simple de boucles JavaScript

N’étant pas resté au bout de la soirée webperf du 21 avril, je ne me risquerais pas à faire un compte rendu qui risquerait d’être incomplet.

Néanmoins, lors de cette soirée nous avons découvert un morceau de code assez surprenant.

Pour se mettre dans le contexte, nous étions en train d’analyser le site de 20 minutes, et
Vincent Voyer
à décidé de lancer un dynatrace.

En regardant les résutlats, nous avons remarqué qu’un script avait un temps d’execution de… 1.1s, ce qui est bien entendu énorme ! Ce script vennait du tag xiti. Celui-ci sera peut-être corrigé, mais il pourra servir de cas d’exemple sur une mauvaise pratique.

En fouillant dans le code on découvre (en réindenté) le code suivant :

var elts = target.getElementsByTagName('*');
for (var i = 0; i < elts.length; i++) {
...
}

Pourquoi est-ce un problème ? Avant de commencer à expliquer le problème, il est important de connaitre un point important. Pour celui-là, j’ai utilisé statsy.

JS in HTML attributes: 1351 bytes
CSS in HTML attributes: 2790 bytes
JS in SCRIPT tag: 4111 bytes
CSS in STYLE tag: 69039 bytes
All innerHTML: 215207 bytes
# SCRIPT tag: 54
# STYLE tag: 1
# DOM elements: 2143
Cookie size: 340 bytes

Dans la liste des résultats ci-dessus, on remarque que la page comporte 2143 élements. Le problème est simple :
Le getElementsByTagName récupère tout les éléments DOM. Ce qui demande beaucoup de traitement sur une page kilométrique comme celle d’un site de news.

Second problème, la boucle effectuée sur ces éléments recalcule cette selection à chaque itération de la boucle. Les élements sont donc recalculés 2143 fois. C’est simplement énorme.

Comment corriger le problème ? Pour le premier, il faut savoir si cette selection est vraiment importante. Qu’à-t-on besoin de faire sur tout l’arbre DOM ?
Pour le second, il suffit de mettre en cache la taille du tableau, par exemple comme ceci :

var elts = target.getElementsByTagName('*');
for (var i = 0, len = elts.length; i < len; i++) {
...
}

Et dans ces conditions, le recalcul ne sera pas refait pour chaque itération.

Conclusion :
De plus en plus, les sites sont remplis de JavaScript. Ces améliorations peuvent sembler superflues, mais c’est ce qui peut faire la différence entre deux site.
Si vous n’en êtes pas convaincus, pensez aux utilisateurs de netbook, de tablettes ou de mobile : Vous allez leur faire gagner un temps fou !

Publicités

Auteur : Bruno Sabot

Développeur Front end, Mobile, Ergonomie, UX

5 réflexions sur « Quick Tip – Optimisation simple de boucles JavaScript »

  1. En fait, je me demande si le .length est recalculé à chaque fois parce que getElementsByTagName retourne une live list.

    Si tu as le temps, je serais curieux de connaître le temps passé si on utilise querySelectorAll (qui retourne une liste pas live).

    (après, c’est toujours une bonne pratique avec JS de cacher les valeurs en local, c’est clair)

    J'aime

  2. Il y a deux problématiques différentes :
    le recalcul de .length sur un tableau
    l’accès à une propriété d’un objet « HTMLCollections » (c’est le var elts = target.getElementsByTagName(‘*’);), lorsqu’on accède à .length d’un tableau HTMLCollections (un tableau d’objets DOM) alors on traverse à chaque fois le pont entre JS DOM et c’est ça qui coute cher (le nombre d’accès DOM).

    Voir sur http://www.phpied.com/dom-access-optimization/ pour quelques cas basiques d’optimisation DOM

    J'aime

  3. Le problème du temps passé est qu’il va dépendre de pas mal de chose : L’ordinateur, le navigateur, la taille de la page, le nombre de logiciels qui tournent en parallèle, etc.
    Difficile donc de donner un temps, et quand bien même, il ne serait pertinent que pour une machine en question pour des cas de test.

    Pour le reste, Vincent à répondu comme il se doit

    J'aime

  4. Testé sur la Home de 20minutes : aucune différence entre les 2 boucles « for »

    console.time(‘loop’);
    var elts = document.getElementsByTagName(‘*’);
    // for (var i = 0; i < elts.length; i++) {
    for (var i = 0, len = elts.length; i < len; i++) {

    }
    console.timeEnd('loop');

    PS: Au passage, salut bruno 😉

    J'aime

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion /  Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion /  Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion /  Changer )

w

Connexion à %s