jeudi 31 mars 2011

jQuery : Les bonnes pratiques

Toujours faire une recherche à partir d’un ID

Le sélecteur le plus rapide de jQuery est le sélecteur d’ID ($(‘#mon-id’)) car elle est directement mappée sur une méthode Javascript native : getElementById().

Sélection d’un élément unique

<div id="content">
 <form method="post" action="/">
  <h2>Traffic Light</h2>
   <ul id="traffic_light">
    <li><input type="radio" name="light" value="red" />Red</li>
    <li><input type="radio" name="light" value="yellow" />Yellow</li>
    <li><input type="radio" name="light" value="green" />Green</li>
   </ul>
   <input class="button" id="traffic_button" type="submit" value="Go" />
 </form>
</div>
Une sélection comme celle-ci est plus rapide :
var traffic_button = $(‘#traffic_button’);
Que celle-ci :
var traffic_button = $(‘#content .button’);


Sélection d’éléments multiples

La sélection d’éléments multiples parcours le DOM de manière transversale, cela peut être très long et très couteux en ressources.
Pour minimiser les pertes de performances, toujours effectuer une recherche à partir de l’ID du parent le plus proche :
var traffic_lights = $(‘#traffic_light input’);


Utiliser des balises avant les classes

Le second sélecteur le plus rapide de jQuery est le sélecteur de balises ($(‘head’)). Toujours parce qu’il s’agit d’un mappage vers une méthode native de JavaScript, getElementsByTagName().
<pre lang="html4strict" escaped="true"><div id="content">
 <form method="post" action="/">
  <h2>Traffic Light</h2>
   <ul id="traffic_light">
    <li><input type="radio" name="light" value="red" />Red</li>
    <li><input type="radio" name="light" value="yellow" />Yellow</li>
    <li><input type="radio" name="light" value="green" />Green</li>
   </ul>
   <input id="traffic_button" type="submit" value="Go" />
 </form>
</div>
Il faut donc toujours préfixer une classe par une balise (et se souvenir qu'il faut commencer par un ID) :
var active_light = $('#traffic_light input.on');
Note : Le sélecteur de classe est l'un des plus lents dans jQuery, sous Internet Explorer il boucle sur le DOM complet. Evitez de l'utiliser chaque fois que c'est possible.
Il ne faut jamais préfixer un ID par une balise. Cela ralenti le processus de recherche car jQuery va boucler sur tous les div à la recherche de l'ID 'content' :
var content = $('div#content');
De la même manière il est inutile est redondant de faire une recherche à partir de
plusieurs ID :
var traffic_light = $('#content #traffic_light');


Cacher les objets jQuery

Prenez l'habitude de sauver vos objets jQuery dans des variables (comme dans les exemples précédents).
Par exemple, il ne faut jamais faire cela :
$('#traffic_light input.on').bind('click', function(){...});
$('#traffic_light input.on').css('border', '3px dashed yellow');
$('#traffic_light input.on').css('background-color', 'orange');
$('#traffic_light input.on').fadeIn('slow');
Mais plutôt passer une variable locale avant de continuer vos traitements :
var $active_light = $('#traffic_light input.on');
$active_light.bind('click', function(){...});
$active_light.css('border', '3px dashed yellow');
$active_light.css('background-color', 'orange');
$active_light.fadeIn('slow');
Astuce : Pour se souvenir que notre variable contient un objet jQuery nous pouvons la préfixer avec un $.
Souvenez-vous : ne faite jamais deux fois la même sélection !!

Garder le résultat d'une sélection pour plus tard

Si vous prévoyez d'utiliser le résultat d'une sélection dans une autre partie de votre programme ou devez utiliser la même fonction plusieurs fois, cacher ce résultat dans une variable globale.
En définissant un conteneur global de résultats jQuery nous pouvons les utiliser depuis l'intérieur d'autres fonctions :
// Définissez un objet global (c.à.d. l'objet window)
 window.$my =
 {
 // Initialisez toutes les requêtes qui vont vous servir plus d'une fois.
 head : $('head'),
 traffic_light : $('#traffic_light'),
 traffic_button : $('#traffic_button')
 };
 
function do_something()
 {
 // maintenant vous pouvez utiliser les résultats stockés et les manipuler.
 var script = document.createElement('script');
 $my.head.append(script);
 
// Quand vous travaillez dans les fonctions, continuez de sauvegarder
 // vos résultats de requête jQuery dans votre conteneur global.
 $my.cool_results = $('#some_ul li');
 $my.other_results = $('#some_table td');
 
// Utilisez votre conteneur global comme un résultat de requête classique :
 $my.other_results.css('border-color', 'red');
 $my.traffic_light.css('border-color', 'green');
 }

(Dé)chaînez la fureur

L'exemple du précédent peut également être accompli de la façon suivante :
var $active_light = $('#traffic_light input.on');
$active_light.bind('click', function(){...})
 .css('border', '3px dashed yellow')
 .css('background-color', 'orange')
 .fadeIn('slow');
Cela permet d'écrire moins de code, rendant notre Javascript plus léger.

Utilisez des sous-requêtes

Par exemple, nous pouvons utiliser des sous-requêtes pour récupérer et cacher les champs actifs et inactifs de notre exemple :
<div id="content">
 <form method="post" action="/">
  <h2>Traffic Light</h2>
  <ul id="traffic_light">
   <li><input type="radio" name="light" value="red" />Red</li>
   <li><input type="radio" name="light" value="yellow" />Yellow</li>
   <li><input type="radio" name="light" value="green" />Green</li>
  </ul>
  <input id="traffic_button" type="submit" value="Go" />
 </form>
</div>
var $traffic_light = $('#traffic_light'),
 $active_light = $traffic_light.find('input.on'),
 $inactive_lights = $traffic_light.find('input.off');
Astuce : Vous pouvez déclarer plusieurs variables en les séparant avec des virgules – sauvez des octets !


Limiter les manipulations directes du DOM

L'idée de base ici est de créer exactement ce dont vous avez besoin en mémoire, et ensuite mettre à jour le DOM. Il ne s'agit pas que d'un conseil pour jQuery mais pour Javascript en général.
Par exemple, si vous avez besoins de créer dynamiquement une liste d'éléments,
ne faite pas ça :
var top_100_list = [...], // On suppose qu'il y a 100 strings différents
 $mylist = $('#mylist'); // jQuery sélectionne notre <ul>
 
 for (var i=0, l=top_100_list.length; i<l; i++)
 {
  $mylist.append('<li>' + top_100_list[i] + '</li>');
 }
A la place, nous allons d'abord créer la collection d'éléments dans une chaine avant de l'insérer au DOM :
var top_100_list = [...], // On suppose qu'il y a 100 strings différents
 $mylist = $('#mylist'), // jQuery sélectionne notre <ul>
 top_100_li = ""; // C'est ici qu'on va stocker notre liste
 for (var i=0, l=top_100_list.length; i<l; i++)
 {
  top_100_li += '<li>' + top_100_list[i] + '</li>';
 }
 $mylist.html(top_100_li);
Encore plus rapide, nous devrions toujours encapsuler plusieurs éléments dans une node parente avant l'insertion :
var top_100_list = [...], // On suppose qu'il y a 100 strings différents
 $mylist = $('#mylist'), // jQuery sélectionne notre <ul>
 top_100_ul = '<ul id="#mylist">'; // On stock toute notre liste
 
for (var i=0, l=top_100_list.length; i<l; i++)
 {
  top_100_ul += '<li>' + top_100_list[i] + '</li>';
 }
  top_100_ul += '</ul>'; // On ferme notre liste
 
$mylist.replaceWith(top_100_ul);

Exploiter la délégation d'événement

En Javascript, chaque événement est remonté aux éléments parents le long de l'arborescence du DOM.
C'est extrêmement pratique quand nous voulons que plusieurs éléments fils (nodes) appellent une même fonction.
Plutôt que d'associer une fonction de listener d'événements à plusieurs nodes (très peu efficace) vous pouvez l'associer une fois pour toute à leur parent, en le laissant se débrouiller avec l'élément fils qui a déclenché l'événement.
Par exemple, nous développons un gros formulaire avec beaucoup de champs, et nous voulons changer la classe d'un champ lorsque ce dernier est sélectionné.
Une association telle que celle-ci n'est pas du tout optimisée :
$('#entryform input).bind('focus', function(){
  $(this).addClass('selected');
}).bind('blur', function(){
  $(this).removeClass('selected');
});
A la place, nous devrions surveiller les événements de focus et de blur au niveau du parent :
$('#entryform).bind('focus', function(e){
 
var cell = $(e.target);      // e.target récupère l'élément qui a
 cell.addClass('selected');  // déclenché l'événement.
 
}).bind('blur', function(e){
 
var cell = $(e.target);
 cell.removeClass('selected');
 
});
L'élément parent agit comme un dispatcher et peut ainsi travailler sur l'élément fils cible qui a déclenché l'événement. Si vous associez un même listener à plusieurs éléments, faite attention, vous êtes en train de coder quelque chose de très lent !

Eliminez les requêtes superflues

Même si jQuery échoue proprement quand il ne trouve pas d'élément, cela ne veux pas dire qu'il ne perd pas de temps à les chercher !
Si vous avez un fichier Javascript global pour votre site, il est tentant d'exécuter toutes vos recherches dans
$(document).ready(function(){ // tout mon joli code })
C'est mal ! N'exécutez que les fonctions applicables à la page courante.
La façon la plus efficace de faire cela est d'utiliser des fonctions d'initialisation légère pour que votre template ait le contrôle complet sur où etquand est exécuter votre Javascript.
Par exemple, vous pouvez inclure le code suivant avant la fermeture de la balise body :
<script type="text/javascript>
  mylib.article.init();
 </script>
</body>
Si votre template de page inclus une variété de modules qui peuvent être ou ne pas être dans la page, ou si pour d'autre raison vous avez besoins de les initialiser plus tôt vous pouvez placer la fonction d'initialisation immédiatement après le module.
<ul id="traffic_light">
 <li><input type="radio" name="light" value="red" /> Red</li>
 <li><input type="radio" name="light" value="yellow" /> Yellow</li>
 <li><input type="radio" name="light" value="green" /> Green</li>
 </ul>
 <script type="text/javascript>
 mylib.traffic_light.init();
 </script>
Votre bibliothèque javascript globale ressemblerait à ça :
var mylib =
{
  article_page :
  {
   init : function()
   {
   // Mes fonctions jQuery spécifiques à la page d'article.
   }
  },
  traffic_light :
  {
  init : function()
   {
   // Mes fonctions jQuery spécifiques à la page trafic.
   }
  }
}

Déléguez à $(window).load

Il y a une tentation parmi tous les développeurs jQuery à associer toutes leurs fonctions à l'événement $(document).ready.
Après tout, il est utilisé dans la plupart des exemples que vous trouverez sur le web, pourquoi changer ?
$(document).ready se déclenche pendant le rendering de la page par le navigateur, c'est-à-dire au même moment où des éléments sont encore téléchargé par ce dernier.
Si vous remarqué un ralentissement du navigateur durant le chargement de la page, toutes ces fonctions $(document).ready en sont peut-être la raison.
Vous pouvez réduire l'utilisation CPU en affectant toutes vos fonctions jQuery à l'événement $(window).load qui est déclenché après que tous les objets appelés par le HTML soit téléchargés. (Y compris le contenu des iframes).
$(window).load(function(){
 // Fonctions jQuery a initialiser après que la page ait été chargée.
 });
Les fonctionnalités "superflues" telles que les drap-n-drop, effets visuels et animations, pré-chargements des images... etc... sont de bonnes candidates pour cette technique.

Aucun commentaire:

Enregistrer un commentaire