Javascript est un langage qui a plein d’idiomes bien à lui. En effet, c’est un langage très puissant, et aussi plein de couilles velues planquées ici et là. Les ninjas JS ont donc créée des astuces pour pallier à ces problèmes, en utilisant la force de leur outil.
Un des gros soucis en JS, c’est qu’il dépend beaucoup des variables globales, qui sont une grosse source d’ennuis en tout genre. Et il est très facile de déclarer ou d’utiliser une variable globale par erreur.
Pour limiter ce problème, on utilise la technique de la fonction anonyme immédiatement appelée. Cela fonctionne comme ceci, au lieu de faire:
alert('rouge'); |
On fait:
(function(){ alert('rouge'); })() |
Explication pas à pas
En effet, en Javascript on peut créer une fonction sans lui donner de nom. C’est ce qu’on appelle une fonction anonyme:
function(){ alert('rouge'); } |
On peut attribuer cette fonction à une variable.
var une_variable_qui_passait_par_la = function(){ alert('rouge')}; |
Et derrière on peut appeler la fonction ainsi:
une_variable_qui_passait_par_la(); |
Mais dans notre cas, on ne met pas la fonction dans une variable, on l’enferme dans des parenthèses, ce qui garde la référence le temps de l’appeler, et on l’appelle. On fait la même chose que précédement en fait, on saute juste une étape :
(function(){ alert('rouge');})() |
La référence à la fonction est perdue, on ne pourra pas l’appeler de nouveau. Mais on s’en branle fortement, on voulait juste l’appeler une fois de toute façon.
Pour rendre le code plus lisible, on le met sur plusieurs lignes:
(function(){ alert('rouge'); })() /* on éxécute la fonction cash pistache */ |
Maintenant pour comprendre ce que ça fait, il faut juste réaliser que le bout de code ci-dessous fait EXACTEMENT la même chose que le bout de code juste au dessus :
alert('route'); |
Car on déclare la fonction, mais on l’éxécute tout de suite. Donc dans les deux cas, au chargement du script, un aura une alerte immédiatement.
Mais putain si ça fait la même chose, pourquoi tu nous gonfles avec ça ?
Parceque dans le cas du dessus ça fait la même chose, mais ça a des propriétés en plus. Vous vous doutez bien qu’on tape pas 4 lignes en trop pour le plaisir (à part en Java).
En effet, en créant une fonction, on crée un scope. Ainsi, tout ce qui est déclaré avec var
dans la fonction sera garanti de ne pas être une variable globale. Pas d’effet de bord sur le monde extérieur. Et le monde extérieur n’a PAS accès aux variables déclarées dans la fonction. Par contre la fonction a accès aux variables globales !
Par exemple, dans un navigateur, vous avez toujours accès à l’objet window
et l’objet Date
partout dans le code.
/* Ici vous avez accès à window et Date */ (function(){ /* Ceci ne va pas changer l'objet window en dehors de la fonction car on redéclare la variable, à l'intérieur du scope de la fonction. */ var window = '3.1'; var rideau = 'vista'; /* Ici vous avez accès à Date */ alert(new Date()) })() /* Ici vous n'avez PAS accès à "rideau" */ |
Il est donc une bonne pratique de mettre TOUT son script dans une fonction qui s’auto-appelle, pour l’isoler du monde exterieur et ne pas pourrir le namespace global. Si on avait pas fait ça, et qu’un autre script définissait aussi rideau
, l’un des deux aurait écrasé l’autre. Et dans tous les cas on aurait écrasé window
.
Encore mieux
Comme on crée un conteneur isolé, on peut faire ce que l’on veut dedans. Et on a deux moyens de communique avec ce conteneur. D’abord, les variables globales. Ensuite, les paramètres de la fonction auto-appelée.
On peut exposer le contenu de la fonction à travers les variables globales. Par exemple, vous faites un générateur de ‘pouet’, chose importante s’il en est :
(function(){ /* ces variables ne sont pas accessibles depuis l'extérieur */ var pouetGenerator = {}; var variablePrivee = true; /* ces fonctions sont accessibles seulement depuis pouetGenerator */ pouetGenerator.fairePouet = function(){ alert('pouet') } pouetGenerator.nePasFairePouet = function(){ if (variablePrivee) { alert('pas pouet'); } } /* On rend notre pouetGenerator accessible au reste du monde */ window.pouetGenerator = pouetGenerator; })() /* On peut utiliser notre générateur de pouet partout ailleurs */ pouetGenerator.nePasFairePouet() |
En effet, tout ce qui est attaché à window
devient une variable globale dans un navigateur. C’est une caractéristique de Javascript dans un navigateur Web: window
est un objet accessible partout, et on a accès partout à ses attributs. On a donc notre code parfaitement isolé (la seule chose exposée c’est notre point d’entrée : pouetGenerator
). On ne pollue pas le namespace. En prime personne n’a accès aux variables privées comme variablePrivee
;
On peut aussi utiliser les paramètres et les valeurs de retour pour cela :
/* pouetGenerator, déclaré sans "var", est une variable globale */ pouetGenerator = (function(chaine_du_pouet){ /* ces variables ne sont pas accessibles depuis l'extérieur */ var pouetGenerator = {}; var variablePrivee = true; /* ces fonctions sont accessibles seulement depuis pouetGenerator */ pouetGenerator.fairePouet = function(){ alert(chaine_du_pouet) } pouetGenerator.nePasFairePouet = function(){ if (variablePrivee) { alert('pas pouet'); } } /* On rend notre pouetGenerator accessible au reste du monde en la retournant, ce qui va mettre la référence dans le pouetGenerator qui est la variable globale. */ return pouetGenerator; /* On passe 'pouet' en paramètre, il va se retrouver dans chaine_du_pouet */ })('pouet') /* On peut utiliser notre générateur de pouet au même niveau */ pouetGenerator.nePasFairePouet() |
Il y a deux différences dans ce code. La première, c’est qu’on met la variable à disposition en la retournant plutôt qu’en l’attachant à document
. La seconde, c’est qu’on passe 'pouet'
.
On pourrait se dire que l’interêt est limité, mais considérez l’exempe suivant:
(function($){ $('p').css('color', 'rouge-fluo') })(jQuery.noConflic()) |
jQuery.noConflic()
restaure $
à sa valeur précédente, permettant à d’autres libs d’utiliser $
(comme la lib prototype.js). Mais jQuery.noConflic()
retourne aussi l’objet jQuery
, que l’on passe en paramètre. Ce paramètre est nommé $
dans la signature de la fonction. Dans notre fonction, on peut donc QUAND MÊME utiliser $
pour faire référence à jQuery
car tout ce qui est dans la fonction est isolée du reste du monde. Et le reste du monde peut utiliser $
pour autre chose que jQuery
.
Best practice
Généralement un code de production pour un script complet ressemble donc à ça:
;(function(alias){ "use strict"; /* tout le code du script va ici */ var une_variable_privee; window.une_variable_exposee = 'foo'; })(truc_a_aliaser) |
Notez le ;
au début qui permet de rajouter votre script à la suite d’un autre script même si celui-ci a oublié de mettre un ;
sur sa dernière ligne (utile pour les minifieurs).
Notez aussi le "use strict";
qui dit au navigateur que tout ce qui se trouve dans cette fonction devra être traité par un parseur strict qui vous signalera plus d’erreurs de code. Cela ne s’applique qu’à la fonction, laissant le reste du monde le droit d’utiliser du code moins strict.
Je je ne m’abuse, l’accès aux variables globales en javascript se fait avec
window
et nondocument
.Juste une petite erreur dans le dernier code :
Il manque la parenthèse fermante, ça devrait être :
“use strict”;
wtf ? Et en plus c’est dans la norme ecma ça ?
@Ghusse Wooops. Je confond toujours les deux donc je test chaque fois dans la console quand je code, et j’oublie.
@CrEv: bien vu, fixed.
@desfrenes: oui et c’est même assez malin: c’est juste une string, donc ignorée par les navgateurs incompatibles, mais pris en compte au besoin. Ca permet d’avoir un javascript moins con.
J’aime quand on parle de JS.
Mais je suis déçu par le manque d’illustration, votre esprit tordu serait il a court de ressources?
J’apprécie le “pouet” qui déconfuse l’esprit.
Et avec un morceau pareil c’était pas du luxe.
“Ca permet d’avoir un javascript moins con”
mmm… ok. Mais bon… ça surprend un peu.
Un article que j’ai trouvé pas mal pour y voir plus clair: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
En fait, ça active des vérifications supplémentaires par le navigateur.
En gros, ça lève plus souvent des exceptions quand on fait des trucs pas nets.
@defresnes: C’est vrai que ça mérite un article.
@anon: c’est surtout que cette salope de max ne branle rien sur le blog en ce moment, et comme c’est le plus pervers de deux, on perd en cochoneries. Il faut faire pression sur lui. Max ! Laisse deux minutes ton optimisation des screenshots anals et viens poster un article avec de la moule un peu !
Ah, les fonctions anonymes, ça me rappelle le langage Lua où je n’utilisais presque que ça pour les callbacks. C’est mon plus gros manque en python, il faut déclarer plein de fonctions pour les interfaces, beaucoup de blabla pour pas grand chose (souvent les callbacks ne font que 2 ou 3 lignes).
Les lambdas, c’est tellement naze en comparaison…
Donc je viens d’apprendre que ça s’appelle une IIFE et qu’en plus il y a déjà un très bon article en français sur le sujet (c’est rare):
http://www.wewereweb.be/javascript-les-iife-ou-comprendre-les-function/2012/10/13/
Très bon site au passage.
C’est devenu pas obsolète ?
Let
ne marche que dans les browsers qui supportent javascript version 1.7, ce qui exclut IE et est opt-in chez chrome il me semble.sympa de lire du JS pour changer :)
J’avais lu que Facebook utilisait souvent la notation !function(){} au lieu des parentheses englobantes. Je me suis dit a l’epoque que c’etait juste pour faire les malins, mais la je me rends compte que ca sautera plus facilement au yeux que des parentheses, qui peuvent deja englober une fonction anonyme quand on passe un callback par exemple.
On peut aussi en faire pas mal d’autres, des syntaxes “here comes an IIFE”. J’aime bien le tilde que je n’utilise jamais en dehors.
-function(){}
+function(){}
~function(){}
Un autre article intéressant sur le strict mode (par Douglas Crockford himself) : http://www.yuiblog.com/blog/2010/12/14/strict-mode-is-coming-to-town/
C’est une méthode très intéressante pour l’application que je suis en train de développer. Merci pour l’article.
Un cour intéressant et très simple, j’avais deja utilisé plusieurs fois ce genre “d’encapsulation” de fonction mais sans en connaitre l’utilité… Aujourd’hui c’est chose faite grâce à vous !
Merci pour ce cour et j’aime bien le coté bas les couilles du chroniqueur, je vais le suivre avec ses autres articles et me remettre dans le html_5 et le tout le Js qui s’en suis !