Quel est le but d'encapsuler des fichiers Javascript entiers dans des fonctions anonymes comme «(function () {…}) ()»?

584

J'ai lu beaucoup de Javascript récemment et j'ai remarqué que le fichier entier est enveloppé comme suit dans les fichiers .js à importer.

(function() {
    ... 
    code
    ...
})();

Quelle est la raison de cela plutôt qu'un simple ensemble de fonctions constructeurs?

Andrew Kou
la source
6
Puisque j'imagine que cela sera utilisé par beaucoup de gens, n'oubliez pas la fermeture;
dgh
5
Cette technique s'appelle "IIFE" je pense. Cela signifie Immediately Invoked Function Expression en.wikipedia.org/wiki/Immediately-invoked_function_expression
Adrien Be

Réponses:

786

C'est généralement pour nommer (voir plus loin) et contrôler la visibilité des fonctions membres et / ou des variables. Considérez-le comme une définition d'objet. Son nom technique est une expression de fonction immédiatement invoquée (IIFE). Les plugins jQuery sont généralement écrits comme ceci.

En Javascript, vous pouvez imbriquer des fonctions. Donc, ce qui suit est légal:

function outerFunction() {
   function innerFunction() {
      // code
   }
}

Vous pouvez maintenant appeler outerFunction(), mais la visibilité de innerFunction()est limitée à la portée de outerFunction(), ce qui signifie qu'il est privé de outerFunction(). Il suit essentiellement le même principe que les variables en Javascript:

var globalVariable;

function someFunction() {
   var localVariable;
}

En conséquence:

function globalFunction() {

   var localFunction1 = function() {
       //I'm anonymous! But localFunction1 is a reference to me!
   };

   function localFunction2() {
      //I'm named!
   }
}

Dans le scénario ci-dessus, vous pouvez appeler globalFunction()de n'importe où, mais vous ne pouvez pas appeler localFunction1ou localFunction2.

Ce que vous faites lorsque vous écrivez (function() { ... })(), c'est que vous faites du code à l'intérieur du premier ensemble de parenthèses une fonction littérale (ce qui signifie que tout "l'objet" est en fait une fonction). Après cela, vous invoquez automatiquement la fonction (la dernière ()) que vous venez de définir. Donc, le principal avantage de cela, comme je l'ai mentionné précédemment, est que vous pouvez avoir des méthodes / fonctions et propriétés privées:

(function() {
   var private_var;

   function private_function() {
     //code
   }
})();

Dans le premier exemple, vous invoqueriez explicitement globalFunctionpar nom pour l'exécuter. Autrement dit, vous feriez juste globalFunction()pour l'exécuter. Mais dans l'exemple ci-dessus, vous ne définissez pas seulement une fonction; vous le définissez et l' invoquez en une seule fois. Cela signifie que lorsque le fichier JavaScript est chargé, il est immédiatement exécuté. Bien sûr, vous pourriez faire:

function globalFunction() {
    // code
}
globalFunction();

Le comportement serait en grande partie le même, sauf pour une différence significative: vous évitez de polluer la portée globale lorsque vous utilisez un IIFE (en conséquence, cela signifie également que vous ne pouvez pas invoquer la fonction plusieurs fois car elle n'a pas de nom, mais puisque cette fonction est uniquement destinée à être exécutée une fois que ce n'est vraiment pas un problème).

La chose intéressante avec les IIFE est que vous pouvez également définir des choses à l'intérieur et exposer uniquement les parties que vous souhaitez au monde extérieur (un exemple d'espacement de noms afin que vous puissiez essentiellement créer votre propre bibliothèque / plugin):

var myPlugin = (function() {
 var private_var;

 function private_function() {
 }

 return {
    public_function1: function() {
    },
    public_function2: function() {
    }
 }
})()

Vous pouvez maintenant appeler myPlugin.public_function1(), mais vous ne pouvez pas y accéder private_function()! Tellement similaire à une définition de classe. Pour mieux comprendre cela, je recommande les liens suivants pour quelques lectures supplémentaires:

ÉDITER

J'ai oublié de mentionner. Dans cette finale (), vous pouvez passer tout ce que vous voulez à l'intérieur. Par exemple, lorsque vous créez des plugins jQuery, vous passez jQueryou $aimez:

(function(jQ) { ... code ... })(jQuery) 

Donc, ce que vous faites ici est de définir une fonction qui accepte un paramètre (appelé jQ, une variable locale, et connu uniquement de cette fonction). Ensuite, vous invoquez automatiquement la fonction et passez un paramètre (également appelé jQuery, mais celui- ci provient du monde extérieur et fait référence à la jQuery proprement dite ). Il n'y a aucun besoin urgent de le faire, mais il y a certains avantages:

  • Vous pouvez redéfinir un paramètre global et lui donner un nom qui a du sens dans la portée locale.
  • Il y a un léger avantage en termes de performances car il est plus rapide de rechercher des éléments dans la portée locale au lieu d'avoir à remonter la chaîne de portée dans la portée globale.
  • Il y a des avantages pour la compression (minification).

Plus tôt, j'ai décrit comment ces fonctions s'exécutent automatiquement au démarrage, mais si elles s'exécutent automatiquement, qui passe les arguments? Cette technique suppose que tous les paramètres dont vous avez besoin sont déjà définis en tant que variables globales. Donc, si jQuery n'était pas déjà défini comme une variable globale, cet exemple ne fonctionnerait pas. Comme vous pouvez le deviner, une des choses que jquery.js fait lors de son initialisation est de définir une variable globale 'jQuery', ainsi que sa plus célèbre variable globale '$', qui permet à ce code de fonctionner après que jQuery ait été inclus.

Vivin Paliath
la source
14
Très cool, je comprends bien l'espace de noms, mais j'ai vu beaucoup de votre dernier exemple et je n'ai pas pu comprendre ce que les gens essayaient de réaliser. Cela clarifie vraiment les choses.
Andrew Kou
34
Post génial. Merci beaucoup.
Darren
4
Je pense que l'ajout d'un point-virgule de début et de fin ';' rendrait l'exemple complet - De ;(function(jQ) { ... code ... })(jQuery);cette façon, si quelqu'un laissait un point-virgule dans son script, il ne casserait pas le vôtre, surtout si vous envisagez de minimiser et de concaténer votre script avec d'autres.
Taras Alenin
3
joli post, j'aime l'accent mis sur les variables privées. J'aime aussi l'ouverture sur le module-pattern / closures (public_function1 & public_function2) et comment vous passez des variables, même si sortir légèrement de la portée, c'est une belle introduction. J'ai également ajouté une réponse, celle-ci se concentrant sur ce que je suppose être les racines de la syntaxe et des différences entre l'énoncé de fonction et l'expression de fonction et ce que je pense être "juste une convention" vs "la seule façon d'atteindre ce résultat".
Adrien Be
4
Excellent article, je pense que peut-être plus sur la façon dont passer des variables dans la fonction auto-exécutable est bénéfique. Le contexte dans la fonction auto-exécutable est propre - pas de données. Vous pouvez passer dans le contexte en faisant cela, (function (context) { ..... })(this)ce qui vous permet ensuite d'attacher tout ce que vous aimez au contexte parent, l'exposant ainsi.
Callum Linington du
79

En bref

Sommaire

Dans sa forme la plus simple, cette technique vise à envelopper le code dans une portée de fonction .

Il aide à diminuer les chances de:

  • conflit avec d'autres applications / bibliothèques
  • portée polluante supérieure (mondiale la plus probable)

Il ne détecte lorsque le document est prêt - il est une sorte de document.onloadniwindow.onload

Il est communément appelé Immediately Invoked Function Expression (IIFE)ou Self Executing Anonymous Function.

Code expliqué

var someFunction = function(){ console.log('wagwan!'); };

(function() {                   /* function scope starts here */
  console.log('start of IIFE');

  var myNumber = 4;             /* number variable declaration */
  var myFunction = function(){  /* function variable declaration */
    console.log('formidable!'); 
  };
  var myObject = {              /* object variable declaration */
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
})();                           /* function scope ends */

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

Dans l'exemple ci-dessus, toute variable définie dans la fonction (c'est-à-dire déclarée à l'aide var) sera "privée" et accessible UNIQUEMENT dans le cadre de la fonction (comme le dit Vivin Paliath). En d'autres termes, ces variables ne sont pas visibles / accessibles en dehors de la fonction. Voir la démo en direct .

Javascript a une portée de fonction. "Les paramètres et variables définis dans une fonction ne sont pas visibles en dehors de la fonction, et qu'une variable définie n'importe où dans une fonction est visible partout dans la fonction." (extrait de "Javascript: The Good Parts").


Plus de détails

Code alternatif

En fin de compte, le code affiché avant pourrait également être fait comme suit:

var someFunction = function(){ console.log('wagwan!'); };

var myMainFunction = function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
};

myMainFunction();          // I CALL "myMainFunction" FUNCTION HERE
someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

Voir la démo en direct .


Les racines

Itération 1

Un jour, quelqu'un a probablement pensé "qu'il doit y avoir un moyen d'éviter de nommer" myMainFunction ", car tout ce que nous voulons, c'est l'exécuter immédiatement."

Si vous revenez à l'essentiel, vous découvrez que:

  • expression: quelque chose évaluant à une valeur. c'est à dire3+11/x
  • statement: ligne (s) de code faisant quelque chose MAIS il n'évalue pas une valeur. c'est à direif(){}

De même, les expressions de fonction évaluent à une valeur. Et une conséquence (je suppose?) Est qu'ils peuvent être immédiatement invoqués:

 var italianSayinSomething = function(){ console.log('mamamia!'); }();

Donc, notre exemple plus complexe devient:

var someFunction = function(){ console.log('wagwan!'); };

var myMainFunction = function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
}();

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

Voir la démo en direct .

Itération 2

La prochaine étape est la pensée "pourquoi avoir var myMainFunction =si nous ne l'utilisons même pas!?".

La réponse est simple: essayez de supprimer cela, comme ci-dessous:

 function(){ console.log('mamamia!'); }();

Voir la démo en direct .

Cela ne fonctionnera pas car "les déclarations de fonctions ne sont pas invocables" .

L'astuce est qu'en supprimant, var myMainFunction =nous avons transformé l' expression de fonction en une déclaration de fonction . Voir les liens dans "Ressources" pour plus de détails à ce sujet.

La question suivante est «pourquoi ne puis-je pas la conserver comme expression de fonction avec autre chose que var myMainFunction =?

La réponse est "vous pouvez", et il y a en fait plusieurs façons de le faire: ajouter un +, un !, un -, ou peut-être encapsuler une paire de parenthèses (comme c'est maintenant fait par convention), et plus je crois. Comme exemple:

 (function(){ console.log('mamamia!'); })(); // live demo: jsbin.com/zokuwodoco/1/edit?js,console.

ou

 +function(){ console.log('mamamia!'); }(); // live demo: jsbin.com/wuwipiyazi/1/edit?js,console

ou

 -function(){ console.log('mamamia!'); }(); // live demo: jsbin.com/wejupaheva/1/edit?js,console

Donc, une fois que la modification pertinente est ajoutée à ce qui était autrefois notre "Code alternatif", nous revenons au même code exact que celui utilisé dans l'exemple "Code expliqué"

var someFunction = function(){ console.log('wagwan!'); };

(function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
})();

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

En savoir plus sur Expressions vs Statements:


Démystifier les portées

Une chose que l'on pourrait se demander est "ce qui se passe lorsque vous ne définissez PAS la variable" correctement "à l'intérieur de la fonction - c'est-à-dire que vous effectuez une simple affectation à la place?"

(function() {
  var myNumber = 4;             /* number variable declaration */
  var myFunction = function(){  /* function variable declaration */
    console.log('formidable!'); 
  };
  var myObject = {              /* object variable declaration */
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  myOtherFunction = function(){  /* oops, an assignment instead of a declaration */
    console.log('haha. got ya!');
  };
})();
myOtherFunction();         // reachable, hence works: see in the console
window.myOtherFunction();  // works in the browser, myOtherFunction is then in the global scope
myFunction();              // unreachable, will throw an error, see in the console

Voir la démo en direct .

Fondamentalement, si une variable qui n'a pas été déclarée dans sa portée actuelle se voit attribuer une valeur, alors "une recherche dans la chaîne de portée se produit jusqu'à ce qu'elle trouve la variable ou atteigne la portée globale (à quel moment elle la créera)".

Dans un environnement de navigateur (par rapport à un environnement de serveur comme nodejs), la portée globale est définie par l' windowobjet. Par conséquent, nous pouvons le faire window.myOtherFunction().

Mon conseil "Bonnes pratiques" sur ce sujet est de toujours l'utiliser varlors de la définition de quelque chose : que ce soit un nombre, un objet ou une fonction, et même lorsqu'il est dans la portée globale. Cela rend le code beaucoup plus simple.

Remarque:

  • javascript ne pas avoir block scope(Mise à jour: l' étendue de bloc variables locales ajoutées dans ES6 .)
  • javascript a seulement function scope& global scope( windowportée dans un environnement de navigateur)

En savoir plus sur Javascript Scopes:


Ressources


Prochaines étapes

Une fois que vous obtenez ce IIFEconcept, il mène au module pattern, ce qui est généralement fait en tirant parti de ce modèle IIFE. S'amuser :)

Adrien Be
la source
Très utile. Merci beaucoup!
Christoffer Helgelin Hald
Sympa, je préfère la version démo :)
Fabrizio Bertoglio
Une si grande explication. Je vous remercie!
Vikram Khemlani
26

Javascript dans un navigateur n'a vraiment que deux étendues efficaces: l'étendue des fonctions et l'étendue globale.

Si une variable n'est pas dans l'étendue d'une fonction, elle est dans l'étendue globale. Et les variables globales sont généralement mauvaises, c'est donc une construction pour garder les variables d'une bibliothèque pour elle-même.

Gareth
la source
1
Mais la fonction constructeur elle-même n'offre-t-elle pas la possibilité de ses propres variables?
Andrew Kou
1
Oui, chaque fonction définie dans cette bibliothèque pourrait définir ses propres variables locales, mais cela permet aux variables d'être partagées entre les fonctions sans qu'elles fuient à l'extérieur de la bibliothèque
Gareth
@Gareth, donc cela permet des variables "globales" dans une portée (;
Francisco Presencia
2
@FranciscoPresencia "global dans une portée" n'est pas une expression utile, car c'est essentiellement ce que signifie "portée". L'intérêt de l'étendue «globale» est que c'est précisément l'étendue à laquelle toutes les autres étendues ont accès.
Gareth
19

Cela s'appelle une fermeture. Il scelle essentiellement le code à l'intérieur de la fonction afin que les autres bibliothèques n'interfèrent pas avec elle. C'est similaire à la création d'un espace de noms dans les langues compilées.

Exemple. Supposons que j'écris:

(function() {

    var x = 2;

    // do stuff with x

})();

Désormais, les autres bibliothèques ne peuvent pas accéder à la variable que xj'ai créée pour l'utiliser dans ma bibliothèque.

Joel
la source
7
Attention à votre terminologie. L'espace de noms implique que les variables sont accessibles de l'extérieur en adressant l'espace de noms (généralement en utilisant un préfixe). Bien que cela soit possible en Javascript, ce n'est pas ce qui est démontré ici
Gareth
Je suis d' accord , il est pas exactement comme un espace de noms, cependant, vous pouvez fournir des fonctionnalités similaires en retournant un objet avec des propriétés que vous souhaitez faire connaître: (function(){ ... return { publicProp1: 'blah' }; })();. Évidemment, pas parfaitement parallèle à l'espace de noms, mais cela peut aider à penser de cette façon.
Joel
dans votre exemple, x est toujours une variable privée ... Malgré que vous l'enveloppiez dans un IIFE. allez-y et essayez d'accéder à x en dehors de la fonction, vous ne pouvez pas ..
RayLoveless
Votre argument n'est pas valable. Même dans la fonction suivante, les autres bibliothèques ne peuvent pas accéder à x. function () {var x = 2}
RayLoveless
@RayLoveless, je suis d'accord. Je ne contredit pas cette affirmation. En fait, j'ai fait la même affirmation que la dernière phrase de cette réponse.
Joel
8

Vous pouvez également utiliser des fermetures de fonctions en tant que données dans des expressions plus grandes, comme dans cette méthode de détermination de la prise en charge du navigateur pour certains des objets html5.

   navigator.html5={
     canvas: (function(){
      var dc= document.createElement('canvas');
      if(!dc.getContext) return 0;
      var c= dc.getContext('2d');
      return typeof c.fillText== 'function'? 2: 1;
     })(),
     localStorage: (function(){
      return !!window.localStorage;
     })(),
     webworkers: (function(){
      return !!window.Worker;
     })(),
     offline: (function(){
      return !!window.applicationCache;
     })()
    }
kennebec
la source
Que fait le !! faire?
1,21 gigawatts le
!! convertit une valeur en sa représentation booléenne (vrai / faux).
Liam
7

En plus de garder les variables locales, une utilisation très pratique consiste à écrire une bibliothèque à l'aide d'une variable globale, vous pouvez lui donner un nom de variable plus court à utiliser dans la bibliothèque. Il est souvent utilisé pour écrire des plugins jQuery, car jQuery vous permet de désactiver la variable $ pointant vers jQuery, en utilisant jQuery.noConflict (). Dans le cas où il est désactivé, votre code peut toujours utiliser $ et ne pas se casser si vous le faites simplement:

(function($) { ...code...})(jQuery);
Coronus
la source
3
  1. Pour éviter les conflits avec d'autres méthodes / bibliothèques dans la même fenêtre,
  2. Évitez la portée globale, rendez-la locale,
  3. Pour accélérer le débogage (portée locale),
  4. JavaScript n'a qu'une portée de fonction, il aidera donc également à la compilation des codes.
Vivek Mehta
la source
1

Nous devons également utiliser 'use strict' dans la fonction scope pour nous assurer que le code doit être exécuté en "mode strict". Exemple de code illustré ci-dessous

(function() {
    'use strict';

    //Your code from here
})();
Neha Jain
la source
Pourquoi devrions-nous utiliser strict?
nbro
Consultez cet article: stackoverflow.com/questions/1335851/…
Neha Jain
Ne répond pas vraiment à la question!
Pritam Banerjee
Pritam, c'est une bonne pratique d'utilisation. Veuillez faire des recherches appropriées avant de voter contre toute réponse
Neha Jain
1
'use strict' sauve les mauvais programmeurs d'eux-mêmes. Et comme la majorité des programmeurs sont de mauvais programmeurs, cela les empêche de faire des choses qu'ils ne devraient certainement pas faire et de se retrouver dans un désordre de code qui s'enfonce rapidement.
MattE