Overblog Suivre ce blog
Editer l'article Administration Créer mon blog
29 mai 2009 5 29 /05 /mai /2009 23:00

Attaquons-nous au petit bout de code dont le mode d'emploi se trouve ici. Ce bout de code est très simple, il nous donnera l'occasion de présenter quelques techniques utiles.

Revoici d'abord le source HTML/JavaScript :

 <div><script> (function () { var r=document.getElementsByTagName('body')[0]; r.className+=r.className ? ' JS_on' : 'JS_on'; })(); </script></div> 

Pourquoi mettre tout ça dans un div ?

C'est pour contrer une initiative de l'éditeur OB et s'assurer que le code JavaScript est bien exécuté dès le début du chargement de la page. Sur d'autres plateformes de blog il est possible que ce div ne serve à rien – cependant il n'est jamais nuisible.

Où est le nom de la fonction ? pourquoi toutes ces parenthèses ?

Ah ça, c'est très joli ! Je le garde pour la fin de l'article !

Qu'est-ce qu'elle fait au juste, cette fonction ?

La première instruction récupère dans un tableau les références de tous les éléments body du document et ne retient que le premier (et unique) élément de ce tableau (indice zéro entre crochets).

La deuxième instruction ajoute  quelque chose  à la liste des noms de classes de cet élément. Ce  quelque chose  est un peu subtil :

  1. si le body a déjà une (liste de) classe(s) le  quelque chose  est la chaîne de caractères  JS_on (avec espace de séparation en tête),
  2. si le body n'a pas encore de classe le  quelque chose  est JS_on (sans espace en tête).

Pourquoi cette distinction ? La solution apparemment suffisante serait de toujours mettre un espace avant le nom de la classe ajoutée, qu'il y ait ou non déjà d'autres classes. Oui, mais certains navigateurs pointilleux n'acceptent pas qu'une liste de classes commence par un espace, alors autant se prémunir.

Autre question ? Autre question : pourquoi je ne vois pas de if pour faire le test ? Bonne question. À droite du signe égale on lit ceci :

 r.className ? ' JS_on' : 'JS_on' 
… qui est une expression conditionnelle.

On pourrait la comprendre ainsi : "question ? réponse 1 : réponse 2". Avant le point d'interrogation ? se trouve une expression logique, vraie ou fausse. Certes, r.className est une chaîne de caractères – mais on peut aussi la considérer comme une expression logique : vraie si r.className est une chaîne non vide (attention ! même un espace n'est pas une chaîne vide !) et fausse si r.className est une chaîne vide ou n'est pas définie.

D'accord ? Après la question viennent les deux réponses possibles, séparées par un point double : : on retient la première si l'expression logique est vraie, la deuxième si l'expression est fausse.

Ceci pourrait s'écrire, bien sûr, avec un if ... else :

 if (r.className) r.className+=' JS_on'; else r.className+='JS_on'; 
… mais c'est plus fastidieux.

Voilà qui termine l'étude du programme.

Alors, cette histoire de nom et de parenthèses ?

On y vient ! Si vous avez déjà lu quelques-uns de mes "démontages", vous avez vu que j'enferme toujours le traitement dans une fonction qu'on exécute ensuite :

 function maFonction() {/* ici des instructions JS*/ } maFonction(); 

Ce programme se compose, en tout et pour tout, de deux instructions : 1- définir la fonction nommée maFonction, 2- l'exécuter en l'appelant par son nom. Tout le reste  n'est que  le développement de la première instruction (ça peut être gros, je l'admets !).

Parlons des parenthèses. Celles de la première instruction indiquent les arguments utilisés dans la définition de la fonction. Dans notre programme il n'y en a aucun, dans d'autres cas il peut y en avoir. Nommons x et y les deux arguments d'une fonction ridicule qui additionne les deux :

 function somme(x,y) {return x+y} 

Les parenthèses de la deuxième instruction (l'appel de la fonction) indiquent les valeurs données aux arguments pour cet appel particulier de la fonction. Dans notre programme il n'y en a, bien sûr, aucun. Ceux de notre fonction ridicule sont les deux nombres à additionner :

 var s; s=somme(1,2) ; /* s vaut 3*/ s=somme( 2*s,4); /* s vaut 10 : 2*3 +4 */ 

Vous savez qu'il existe un autre usage des parenthèses, analogue à celui des mathématiques, pour imposer l'ordre des calculs :

 var s; s= 5 + 4*2; /* s vaut 5+8=13*/ s= (5 + 4)*2; /* s vaut 9*2=18*/ 

Pour le dire en clair : on évalue d'abord ce qui se trouve entre les parenthèses. Et s'il y a des parenthèses dans les parenthèses, on évalue en premier les parenthèses les plus "profondes". Si vous avez déjà fait un peu de programmation, je ne vous ai rien appris jusqu'ici.

Maintenant, regardons de près l'appel de fonction :

 maFonction(); 

Paraphrase : "prendre ce qui s'appelle maFonction et l'appliquer aux (zéro) arguments indiqués". Clair ? Œuf corse, on n'obtient un résultat que si la variable nommée maFonction est définie et contient du code de fonction.

Question faussement naïve : qu'est-ce qui s'oppose à ce qu'on utilise, au lieu de la variable maFonction, "quelque chose" (une expression) dont le résultat serait la définition d'une fonction ? Rien, à part que nous ne savons pas trop comment écrire ce "quelque chose". Attendez un peu…

Définir une fonction, en JavaScript, se fait le plus souvent comme ça :

 function maFonction() {/* instructions JS*/} 

Mais on peut aussi, puisque le nom d'une fonction est une variable comme les autres, écrire ceci :

 var maFonction= function () {/* instructions JS*/}; 

Notez l'ajout du point-virgule final : ceci (comme l'indique le signe "égale") est l'affectation d'une valeur à une variable, il faut donc la terminer par un point-virgule, c'est la loi. Il se trouve juste que la valeur est un peu plus compliquée que d'habitude mais, à cela près, le résultat est exactement le même.

Quel intérêt ? D'abord cette forme permet de définir maFonction différemment selon les circonstances, ce que ne permet pas la première forme. Ensuite cette forme répond à notre question : le "quelque chose" dont le résultat serait la définition d'une fonction… hé bien ce "quelque chose" est écrit à droite du signe égale ! Le voilà :

 function () {/* instructions JS*/} 

Pour "l'appliquer aux (zéro) arguments indiqués" on le met entre parenthèses, pour être certain qu'il sera évalué avant de poursuivre :

 (function () {/* instructions JS*/}) 
… et on l'applique aux zéro arguments :
 (function () {/* instructions JS*/})() ; 

Je lis dans vos pensées :  Ah certes, c'est beau et ça marche mais ne serait-ce pas un peu, euh, comment dire… de la sodomie de diptères ?  Regardons ça.

Étape 1 : un programme JS écrit sans précautions particulières, tel qu'on en trouve beaucoup trop dans la nature, ressemble à ça :

 var i,j,k; /* faire des choses avec i, j et k*/ /* et aussi avec truc, variable non déclarée */ 

Dans ce cas i et les autres variables, et même truc, sont des variables globales, accessibles et modifiables dans l'ensemble du code JavaScript. Si, avant ou après ce programme, vous en exécutez un autre sans aucun rapport mais qui utilise aussi i, j, k ou truc, l'un des deux programmes risque fort d'utiliser les valeurs définies par l'autre. D'où caca-beurk intense.

C'est pourquoi, étape 2, chacun de mes programmes : d'abord décrit son travail dans une fonction, ensuite appelle cette fonction. Ainsi les variables définies à l'intérieur de la fonction ne sont connues qu'à l'intérieur de cette fonction, ce sont des variables locales à la fonction. Une variable i définie dans maFonction ne pourra pas être confondue avec une autre variable i définie dans uneAutreFonction.

Ce procédé est déjà nettement plus sûr que de ne pas utiliser de fonction  enveloppe  mais il reste un hic : le nom maFonction est lui-même celui d'une variable globale ! Avec tous les risques de confusion présentés ci-dessus. C'est, entre autres raisons, ce pourquoi je choisis des noms de fonction longs et expressifs, plutôt que f1, f2 ou autres. N'empêche qu'il subsiste un petit risque.

Il en subsiste un autre : cette fonction maFonction est, en général, destinée à ne servir qu'une seule fois. Mais, puisque c'est une variable globale, elle demeure en mémoire du navigateur jusqu'à ce qu'on décharge la page. C'est-à-dire que tout le code de la fonction reste en mémoire. Pour mes fonctions qui sont, le plus souvent, brèves ce n'est pas trop gênant, mais que se passera-t-il si on multiplie ce genre de petites choses ? Ou si l'on a besoin d'une énorme fonction avec de gros tableaux de données à l'intérieur ? Réponse : on charriera des kilo- voire des méga-octets inutiles.

Ça peut faire très mal sur une machine pauvre en mémoire, ou sur un téléphone mobile. Il faudrait pouvoir détruire cette fonction après emploi. delete maFonction; ? L'instruction delete existe bien mais, hélas, ne marche pas avec les variables globales. Sont très coriaces, ces bestiaux-là

Et c'est là, étape 3, que la drôle de manière d'écrire prend toute sa justification :

  1. la fonction n'a pas de nom, elle est anonyme. Pas de variable globale, pas de risque de collision entre variables globales : sécurité.
  2. la fonction est définie à la volée, juste au moment de son emploi. Après exécution il n'y a ni raison de la garder en mémoire ni moyen d'y faire référence puisqu'elle est anonyme. Sa place en mémoire est récupérable : propreté.

Pourquoi tous tes programmes ne sont pas bâtis comme ça, alors ?

Tout simplement parce que je n'ai pas compris tout de suite tout ce que je viens de vous raconter ! Mais il n'est pas exclu que je remette tout à niveau, petit à petit.

Recette pratique

Même et surtout si vous n'avez rien compris de ce qui précède, retenez ceci : quand vous récupérez un programme JS quelque part, et même si vous n'êtes pas un as en JS, regardez à quoi ressemble le début de ce programme. S'il commence comme ça :

 // Commentaires /* d'autres commentaires */ var i, truc, machin; ....... 
emballez-le en le complétant comme ceci :
 (function() { // toute première ligne ...... le programme.... })(); // toute dernière ligne 

Cela devrait venir à bout de bien des programmes qui marchent parfaitement chez leur auteur et pas du tout dans votre blog plein de gadgets mirifiques…


À toutes fins utiles : quelques bonnes adresses pour en savoir plus sur JavaScript.

par Aïe mes doigts ! - dans JavaScript Mania
commenter cet article

commentaires

Sagan33 24/05/2012 11:24

Excuse la question bête : si je copie ton code du haut, entre les div, dans un module texte libre, en html, en haut de ma page article, concrètement que se passera-t-il dans cette page ?
Pardon d'en faire rire certaines, mais très novice en matière de javascript.
Bonne journée.

AïmD ! 24/05/2012 15:31



Boudiou ! 2 commentaires pour un article hermétique, j'en reste sur le... siège ! :-D


Les questions les plus bêtes sont celles qu'on n'ose pas poser, donc pas de complexe. Que se passera-t-il ? Comme souvent avec mes programmes : rien si on ne touche pas au CSS. As-tu lu l'article
mentionné au début ? C'est lui qui explique (tente de...) ce qu'on peut espérer obtenir. N'hésite pas à poser d'autres questions sur cet article (et puis, son introduction devrait te convenir ;-)
)



Flo-Avril 24/05/2012 10:14

Tu vas, c'est quand même grâce à ton codage pour la fenêtre de commentaire, que j'ai appris à décomposer le tout.
Merci encore pour ton aide
Amitiés, Flo

AïmD ! 24/05/2012 15:26



A décomposer le tout ? Je ne suis pas certain de saisir ce dont tu parles, mais j'apprécie le mot gentil ! :-)



Archives