Présentation des sélecteurs

Un sélecteur JavaScript ou CSS est une méthode pour identifier un élément situé dans une page HTML. Pour appliquer un style à un élément, il faut donc l’appeler avec un sélecteur dans son code CSS. En JavaScript toutes les balises sont ajoutées dans le DOM qui est une représentation structurée de la page. Dans le même principe que le CSS, pour appeler une balise HTML dans un code JS, il faut l’identifier à partir d’un sélecteur. JavaScript dispose de plusieurs fonctions pour cela comme document.getElementById() ou document.querySelector(). Si on utilise la librairie jQuery, on peut appeler une balise HTML directement avec $(« .ma-classe ») puis appliquer une fonction à l’objet ciblé.

Pour mesurer les performances d’un sélecteur optimisé avec ou sans la librairie jQuery, des tests sont réalisés tout au long de l’article. Cependant il n’est pas possible ou très difficile de mesurer cela au niveau du CSS. En analysant la performance au niveau du JavaScript cela donne une idée globale dont les répercussions pour du CSS semblent être évidentes. Au niveau de la console des outils de développement du navigateur, on retrouve tous les résultats des tests décrits dans cette page web. Ces benchmarks permettent de comparer un sélecteur optimisé ou non et en plus de mesurer cela avec jQuery.

Optimisation des sélecteurs en JavaScript et CSS
Optimiser les sélecteurs en JavaScript et CSS

Les sélecteurs de type et d’attribut

Pour manipuler les éléments d’une page web, il est essentiel de connaître les sélecteurs en JavaScript. Un sélecteur permet d’identifier un élément du DOM en fonction du type (div, span, a, etc.) ou d’un attribut (id, class, href, etc.). L’optimisation des sélecteurs est importante pour éviter de devoir parser le DOM en entier afin d’identifier un élément. En effet, si on doit cibler un div qui possède une certaine classe et qu’on sait qu’il est situé dans le footer de la page, il est important d’ajouter le footer dans le sélecteur pour le cibler plus rapidement. On retrouve exactement le même principe en CSS dans la déclaration de ses styles pour optimiser les performances de construction de la page.

<srcipt>
$(document).ready(function() {
    console.log('Benchmark : simple sélecteur');

    // JS : 0.136 ms
    console.time('test1');
    let test1 = document.querySelector('h1.entry-title');
    console.timeEnd('test1');

    // JS (optimisé) : 0.028 ms
    console.time('test2');
    let test2 = document.querySelector('#main-content .post-header h1.entry-title');
    console.timeEnd('test2');

    // jQuery : 0.344 ms
    console.time('test3');
    let test3 = $('h1.entry-title');
    console.timeEnd('test3');

    // jQuery (optimisé) : 0.139 ms
    console.time('test4');
    let test4 = $('#main-content .post-header h1.entry-title');
    console.timeEnd('test4');

});
</srcipt>

Les exemples avec optimisation du sélecteur montre bien une performance 5 à 10x plus rapide que ce soit en JavaScript ou avec jQuery. Il permet de cibler le titre de la page en indiquant au navigateur dans quelle section du code source HTML il peut le trouver. L’avantage principal est d’aider le navigateur a identifier l’élément ciblé mais le principal désavantage est que si on change la structure de l’HTML alors le sélecteur ne fonctionne plus. Il faut trouver un bon compromis entre un sélecteur ciblé sans toutefois ajouter trop d’éléments. Ajouter 2 à 3 éléments parents dans le sélecteur est un bon ratio. Ce test montre bien qu’un sélecteur optimisé en JavaScript est en moyenne 5 à 10x plus rapide.

Les multisélecteurs

Le benchmark des multisélecteurs va cibler les éléments de la liste des articles récents au niveau de la sidebar puis faire une boucle pour ajouter une classe à chaque lien de la liste. Cet exemple permet de comparer plusieurs fonctionnalités comme la boucle each et l’ajout d’une classe. Le CSS est par défaut multisélecteur, car quand on cible un élément dans le document, ce sont tous les éléments identiques qui sont ciblés. Au final, comme le benchmark précédent, JavaScript natif est 2 à 3x plus rapide que jQuery pour cibler plusieurs balises et réaliser une boucle. Si on optimise un peu son sélecteur, on a un gain de performance de 1,5x par rapport à une sélection directe de l’objet.

<srcipt>
$(document).ready(function() {
    console.log('Benchmark : multi sélecteurs');

    // JS : 0.636 ms
    console.time('test5');
    let test5 = document.querySelectorAll('a.wp-block-latest-posts__post-title');
    test5.forEach(element => element.classList.add('new-class-js'));
    console.timeEnd('test5');

    // JS (optimisé) : 0.421 ms
    console.time('test6');
    let test5 = document.querySelectorAll('#main-content .et_pb_extra_column_sidebar a[href]');
    test5.forEach(element => element.classList.add('new-class-js-optim'));
    console.timeEnd('test6');

    // jQuery (optimisé) : 0.826 ms
    console.time('test7');
    let test6 = $('#main-content .et_pb_extra_column_sidebar a[href]');
    test6.each(function() { $(this).addClass('new-class-jquery'); });
    console.timeEnd('test7');

});
</script>

Les sélecteurs first & last child

Ces sélecteurs permettent de cibler le premier et dernier élément d’une liste ou d’un ensemble de même type. Ils sont très pratiques en CSS pour appliquer un style particulier au premier ou au dernier élément d’une liste ou d’un tableau. Comme dans les tests précédents un sélecteur optimisé en JavaScript natif est 2 à 3x plus rapide pour cibler l’élément. Il existe aussi des fonctions JavaScript et jQuery pour cibler ces éléments comme lastElementChild et last mais utiliser les sélecteurs qui le font de façon très bien est plus propre.

<script>
$(document).ready(function() {
    console.log('Benchmark : sélecteurs first & last child');

    // JS : 0.193 ms
    console.time('test8');
    let test8 = document.querySelector('ul.wp-block-latest-posts li:last-child');
    console.timeEnd('test8');

    // JS (optimisé) : 0.065 ms
    console.time('test9');
    let test9 = document.querySelector('#main-content .et_pb_extra_column_sidebar ul.wp-block-latest-posts li:last-child');
    console.timeEnd('test9');

    // jQuery (optimisé) : 0.109 ms
    console.time('test10');
    let test10 = $('#main-content .et_pb_extra_column_sidebar ul.wp-block-latest-posts li:last-child');
    console.timeEnd('test10');

});
</script>

Le sélecteur not

Enfin pour finir ces benchmarks de performance, le sélecteur not va permettre de cibler au niveau des liens vers les réseaux sociaux en bas de l’article tous les réseaux sauf Twitter. Le sélecteur not permet de retirer un élément d’une liste en fonction d’un attribut. Pour cela l’attribut data-network-name servira à cibler tous les boutons sauf celui qui a la valeur twitter. not est pratique en CSS pour appliquer des styles ciblés sur des balises HTML. Là encore le JavaScript en natif est 2 à 3x plus rapide qu’un sélecteur non optimisé ou que la librairie jQuery.

<script>
$(document).ready(function() {
    console.log('Benchmark : sélecteur not');

    // JS : 0.212 ms
    console.time('test11');
    let test11 = document.querySelectorAll('a.social-share-link:not([data-network-name="twitter"])');
    console.timeEnd('test11');

    // JS (optimisé) : 0.079 ms
    console.time('test12');
    let test12 = document.querySelectorAll('#main-content .post-footer a.social-share-link:not([data-network-name="twitter"])');
    console.timeEnd('test12');

    // jQuery (optimisé) : 0.147 ms
    console.time('test13');
    let test13 = $('#main-content .post-footer a.social-share-link:not([data-network-name="twitter"])');
    console.timeEnd('test13');

});
</script>

Conclusion sur les tests de performance

Pour conclure ces benchmarks, on peut remarquer qu’un sélecteur optimisé en JavaScript natif est en moyenne 2 à 5x plus rapide pour identifier un élément. La librairie jQuery offre un bon compromis et reste tout de même relativement optimisée.

En fonction de ce que l’on recherche dans un projet, intégrer la librairie jQuery permet d’écrire un code plus concis et facile à maintenir. Cependant si on cherche la performance avant tout il est judicieux d’écrire son code en vanilla JS pour maximiser les performances de l’application. Il faudrait comparer des fonctionnalités plus poussées, mais un développement en natif sera toujours plus rapide que n’importe quelle librairie que ce soit jQuery ou une autre. Ne pas confondre une librairie qui ajoute des fonctionnalités à un langage de programmation avec un web framework qui lui permet de structurer son code et de bénéficier de fonctionnalités majeures telles que le routage, la réactivité ou le multilingue comme Angular ou VueJS.

Il existe de nombreux types de sélecteurs en CSS et JavaScript comme les combinateurs (>), les sélecteurs adjacents (+), etc. Savoir bien les utiliser permet de manipuler le document de façon précise et rapide. Il est préférable de privilégier les fonctions natives de JavaScript pour rechercher une ou plusieurs balises dans une page HTML en spécifiant 2 à 3 éléments parents pour accélérer le sélecteur.