PROJET AUTOBLOG


Sam et Max

source: Sam et Max

⇐ retour index

La fonction anonyme appelée immédiatement en Javascript: (function())()

mercredi 2 janvier 2013 à 18:09

Javascript est un langage qui a plein d’idiomes bien à lui. En effet, c’est un langage très puissant, et ausi 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:
pre lang=”javascript”>
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.