Mot-clé - DOM

Fil des billets

mercredi 27 août 2008

Librairies JavaScript : Connaissez vos outils

Certes je n'aime pas les librairies JavaScript en général. Beaucoup de code téléchargé, parsé, exécuté pour n'utiliser la plupart du temps qu'un ensemble très réduit de fonctionnalités. Mais bon, mettons ça de côté aujourd'hui car il faut l'admettre, elles rendent tout de même de bons services.

On va quand même garder un point qui m'embête : ces librairies cachent souvent la complexité du code qu'elles exécutent. Pour démontrer cela, mettons nous en situation ! L'exemple sur la page suivante contient 1729 paragraphes de deux mots. Pour une raison évidente[1], nous voulons cacher le deuxième mot de chaque paragraphe. Pour cela, nous utilisons jQuery de deux manières différentes.

La méthode toggle

$('span').toggle();

Que fait réellement cette simple ligne ? Elle va récupérer tous les éléments <span> de la page puis leur appliquer la méthode toggle. Pour chaque élément trouvé (1729 je rappelle), elle va vérifier s'il est affiché ou non et lui appliqué un style en conséquence. C'est tellement long que votre navigateur risque de bloquer sur la page de test.

La méthode classe

$('div').eq(0).toggleClass('hidden');

Jeu de mot pas terrible mais qui résume bien la situation. Ce code récupère tous les éléments <div> de la page et ne travaille que sur le premier. Sur ce <div>, elle va regarder s'il a la classe hidden et en fonction, lui ajoutera ou enlèvera cette classe. Voilà, c'est tout ce qu'elle fait. Ensuite, c'est le navigateur qui travaille comme un grand grâce à la règle .hidden span {display:none;}. Évidemment, le moteur CSS de votre navigateur fera ça bien plus rapidement.

Conclusion

Évidemment, mon exemple avec autant d'éléments est grossier. Mais tout de même, la différence est fondamentale. Permettez-moi une analogie entre un accès DOM et un accès disque, c'est à dire les parties les plus lentes des deux algorithmes précédents. Dans le premier cas, un premier accès en lecture (récupérer tous les spans) puis 1729 accès en lecture (est-il affiché ?) et 1729 accès en écriture (réglons son affichage). Dans le deuxième cas, nous avons un premier accès en lecture (récupérons tous les divs) puis un accès en lecture (est-ce que la classe est présente ?) puis un accès en écriture (ajoutons ou enlevons la classe). Le calcul est vite fait...

De manière générale, rappelons qu'il faut le moins possible toucher au style d'un élément. Si vous devez accéder plus de deux fois à l'attribut style, il sera plus profitable de passer par une règle CSS. Le moteur CSS sera toujours plus rapide que vous. De plus, vous permettez aux intégrateurs de pouvoir styler votre page en ne changeant que quelques classes. Et votre code d'affichage reste cantonné aux fichiers CSS, merci la maintenabilité.

Note

[1] parce qu'il faut bien un exemple à la con

mardi 3 juin 2008

Nouveautés Safari 3.1 : Selectors API

Présentation

Comme promis, on va parler de quelque chose de bien plus intéressant pour accélérer vos applications web. Une attente de longue date, la fameuse et célèbre Selectors API ! C'est l'équivalent de $$ en Prototype et Mootools, $ en jQuery, YAHOO.util.Selector.query en YUI et... document.querySelectorAll pour base2. [1] Pour ceux qui connaissent, cela permet donc de sélectionner un liste d'éléments qui correspondent à un sélecteur CSS.

L'exemple du billet précédent devient donc document.querySelectorAll('.post'). Une différence à noter entre ces deux méthodes : querySelectorAll retourne une liste statique, un tableau en quelque sorte.

Regardons un peu les performances :

var start = new Date(); for (var i = 0; i < 1000; i++) var list =document.querySelectorAll('.post'); new Date() - start
104
var start = new Date(); for (var i = 0; i < 1000; i++) var list =document.getElementsByClassName('post'); new Date() - start
2

C'est sans appel, pour un sélecteur simple, il vaut mieux utiliser une méthode spécialisée. J'ai fait quelques autres tests. De manière générale, si vous voulez traversez simplement le DOM, tenez-vous en aux autres solutions. Cette méthode sera peut-être optimisée plus tard, ce n'est que la première implémentation.

Évidemment, le navigateur ne reconnaîtra que les sélecteurs qu'il connaît. Pas de problème avec Safari 3.1 puisqu'il connaît tous les sélecteurs disponibles à ce jour (on en reparlera plus tard, peut-être). Par contre, avec IE8, ce sera plus compliqué. Oui oui, IE8 implémentera cette API ! Et comme cette API a été bien conçue, le navigateur est censé renvoyer une exception lorsqu'il ne connaît pas un sélecteur. Le monde est presque parfait !

Les librairies

  • Prototype : dans la version de dev (un appel à un div vide puis le vrai appel)
  • YUI : rien
  • mootools : rien
  • base2 : Oui, en dev.
  • jQuery : rien.

Le piège.

Le comportement de cette API est pour l'instant trompeuse. Lorsque l'on utilise cette méthode sur un élément, elle va chercher tous les éléments du document qui correspondent puis ne retourne que ceux qui sont des enfants de l'élément. Le comportement des librairies (et qui me semble plus naturel) est d'appliquer le sélecteur à partir de l'élément en question. John Resig en parle bien mieux que moi

Les petits plus

Que peut-on faire de marrant avec cette API ? On peut par exemple récupérer tous les liens présents sur la page qu'a visité l'utilisateur. Un simple document.querySelectorAll(':visited') fera l'affaire.

On peut aussi récupérer la liste de tous les éléments qui sont actuellement survolés par l'utilisateur. document.querySelectorAll(':hover') vous donnera satisfaction.

Notes

[1] Oui, cette librairie est tellement bien qu'il n'y a pas besoin d'apprendre autre chose que les standards

jeudi 10 avril 2008

Nouveautés Safari 3.1 : getElementsByClassName

Allez, on continue dans Webkit (parce que ça me botte en ce moment). Je ne suis pas tombé sur beaucoup d'articles en français résumant les nouveautés de Safari 3.1. Et comme on n'est jamais mieux servi que par soi-même...

Commençons par getElementsByClassName. Kékecé ? À l'instar des getElementById, getElementsByTagName, getElementsByName c'est une méthode pour récupérer des éléments du DOM selon un critère, en l'occurence la ou les classes. Par exemple, sur la page d'accueil de ce blog, document.getElementsByClassName('post').length renvoie 20 mais document.getElementsByClassName('post odd').length renvoie 10. Comme toutes ces consœurs, cette méthode marche aussi à partir d'un élément du DOM, restreignant ainsi les résultats.

Il faut faire attention en manipulant la liste renvoyée. Elle est "live", c'est-à-dire que tout changement dans le DOM la modifie (comme getElementsByTagName). Exemple:

var list = document.getElementsByClassName('post');
for (var i=0; i < list.length; i++)
{
  list[i].parentNode.removeChild(list[i]);
  alert(list.length);
}

En exécutant cette fonction sur la page d'accueil, on verra disparaître un billet sur deux et la NodeList diminuera au fur et à mesure. Attention donc en manipulant les résultats. Pour être sûr de ce qui va se passer, on peut recopier le tout dans un tableau classique, mais attention aux performances.

Cette méthode est actuellement supportée par Safari 3.1 (of course) et bientôt Firefox 3 et Opera 9.5. Elle fait partie du brouillon pour HTML 5.

Vous voyez évidemment à quoi ça sert ? Ça accélère considérablement tous les accès qu'on peut faire, si on l'utilise. Et comme de plus en plus de gens utilisent des librairies pour ne pas s'embêter avec les compatibilités entre navigateurs, on va vérifier qu'on bénéficie bien de cette amélioration quand disponible. On va surtout étudier les méthodes du style $ ou $$, qui permettent de sélectionner des éléments à partir d'un sélecteur CSS

Bilan : Ah bah c'est pas génial. Si j'étais mesquin, je dirais que c'est une raison de plus pour ne pas utiliser de librairies Javascript. À leur décharge, ce n'est pas vraiment nécessaire puisqu'elles utilisent XPath lorsque c'est disponible et c'est disponible sur tous les navigateurs ayant cette méthode. Pour jQuery, c'est regrettable, puisqu'elle n'a pas d'implémentation XPath. Bien évidemment, XPath est plus rapide que DOM, mais tout de même plus lent que getElementsByClassName.

Mais finalement, on verra bientôt qu'il y a d'autres méthodes pour nous aider.