web-dev-qa-db-fra.com

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

Je lis beaucoup de Javascript récemment et je constate que tout le fichier est encapsulé comme suit dans les fichiers .js à importer.

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

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

563
Andrew Kou

Il s’agit généralement d’espace de noms (voir plus loin) et de contrôler la visibilité des fonctions membres et/ou des variables. Pensez-y comme une définition d'objet. 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'elle est privée à outerFunction(). Il suit fondamentalement le même principe que les variables en Javascript:

var globalVariable;

function someFunction() {
   var localVariable;
}

De manière correspondante:

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 localFunction1 ou localFunction2.

Lorsque vous écrivez (function() { ... code ... })(), vous créez le code à l'intérieur d'un littéral de fonction (ce qui signifie que l'intégralité de "l'objet" est en fait une fonction). Après cela, vous invoquez vous-même la fonction (le () final). Donc, le principal avantage de ceci, comme je l’ai déjà mentionné, 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, globalFunction () était la fonction publique pouvant être appelée pour accéder à la fonctionnalité publique, mais dans l'exemple ci-dessus, comment l'appelez-vous? Ici, la fonction d’invocation automatique fait que le code s’exécute automatiquement au démarrage. Tout comme vous pouvez ajouter initMyStuff (); Au début de tout fichier .js, qui s'exécutera automatiquement dans le cadre de la portée globale, cette fonction auto-invoquante s'exécutera également automatiquement, bien qu'il s'agisse d'une fonction non nommée, elle ne peut pas être appelée plusieurs fois, comme initMyStuff ().

La chose intéressante est que vous pouvez également définir des éléments à l'intérieur et les exposer au monde extérieur de manière à (par exemple, un espace de noms permettant de créer votre propre bibliothèque/plug-in):

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 accéder à private_function()! Tellement semblable à une définition de classe. Pour mieux comprendre cela, je vous recommande les liens suivants pour une lecture plus approfondie:

EDIT

J'ai oublié de mentionner. Dans ce dernier (), vous pouvez transmettre tout ce que vous voulez à l'intérieur. Par exemple, lorsque vous créez des plugins jQuery, vous transmettez jQuery ou $ comme suit:

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

Vous définissez donc une fonction qui prend un paramètre (appelé jQ, une variable locale et connue seulement ). Ensuite, vous invoquez vous-même la fonction et transmettez un paramètre (également appelé jQuery, mais , celui-ci provient du monde extérieur et fait référence au jQuery actuel. lui-même). Il n’est pas urgent de le faire, mais présente certains avantages:

  • Vous pouvez redéfinir un paramètre global et lui attribuer un nom pertinent 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 l'étendue locale plutôt que d'avoir à parcourir la chaîne d'étendue dans l'étendue globale.
  • Il y a des avantages pour la compression (minification).

J'ai décrit précédemment comment ces fonctions s'exécutent automatiquement au démarrage, mais si elles s'exécutent automatiquement, qui transmet les arguments? Cette technique suppose que tous les paramètres sont définis en tant que variables globales. Donc, si jQuery n'était pas défini comme une variable globale, cet exemple ne fonctionnerait pas et ne pourrait pas être appelé autrement, notre exemple étant une fonction anonyme. Comme vous pouvez le deviner, jquery.js définit lors de son initialisation une variable globale 'jQuery', ainsi que sa plus célèbre variable globale '$', qui permet à ce code de fonctionner après l’inclusion de jquery.js.

768
Vivin Paliath

En bref

Sommaire

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

Cela aide à diminuer les chances de:

  • conflit avec d'autres applications/bibliothèques
  • polluant portée supérieure (globale le plus probable)

Il ne détecte pas si le document est prêt - ce n'est pas une sorte de document.onload ni window.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 de var) sera "privée" et accessible dans l'étendue de la fonction UNIQUEMENT (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 fonction. "Les paramètres et les variables définis dans une fonction ne sont pas visibles en dehors de la fonction et une variable définie n'importe où dans une fonction est visible partout dans la fonction." (de "Javascript: les bonnes parties").


Plus de détails

Code alternatif

En fin de compte, le code posté auparavant 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 exister 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 qui correspond à une valeur. c'est-à-dire 3+11/x
  • statement: ligne (s) de code faisant quelque chose MAIS ne correspond pas à une valeur. c'est-à-dire if(){}

De même, les expressions de fonction sont évaluées en 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 ceci, comme ci-dessous:

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

Voir la démo en direct .

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

Le truc c'est qu'en supprimant var myMainFunction = nous avons transformé l'expression de la 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 vous pouvez le faire de différentes manières: en ajoutant un +, un !, un -, ou peut-être en entourant une paire de parenthèses ( comme cela se fait maintenant par convention), et plus je crois. À titre d'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 a été ajoutée à ce qui était autrefois notre "code alternatif", nous revenons exactement au même code 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 oscilloscopes

Une chose à se demander est "que se passe-t-il lorsque vous ne définissez PAS la variable" correctement "dans la fonction - c'est-à-dire une simple affectation?

(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 .

En gros, si une valeur est affectée à une variable qui n'a pas été déclarée dans son étendue actuelle, "une recherche de la chaîne d'étendue se produit jusqu'à ce qu'elle trouve la variable ou atteigne l'étendue globale (à quel moment elle sera créée).".

Dans un environnement de navigateur (par rapport à un environnement de serveur tel que nodejs), la portée globale est définie par l'objet window. Nous pouvons donc faire window.myOtherFunction().

Mon astuce "Bonnes pratiques" sur ce sujet est de toujours utiliser var pour définir quoi que ce soit : que ce soit un nombre, un objet ou une fonction, & même quand dans la portée globale. Cela rend le code beaucoup plus simple.

Remarque:

  • javascript n'a pas ni block scope (Mise à jour: ajout de variables locales dans ---, ES6 .)
  • javascript a seulement function scope & global scope (window portée dans un environnement de navigateur)

En savoir plus sur Javascript Scopes:


Ressources


Prochaines étapes

Une fois que vous avez obtenu ce concept IIFE, il conduit au module pattern, qui est généralement réalisé en exploitant ce modèle IIFE. S'amuser :)

76
Adrien Be

Dans un navigateur, Javascript n’a réellement que deux champs d’application efficaces: le périmètre fonctionnel et le périmètre global.

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

26
Gareth

Cela s'appelle une fermeture. Fondamentalement, il scelle le code à l'intérieur de la fonction afin que les autres bibliothèques ne l'interférent pas. Cela ressemble à la création d'un espace de noms dans les langages compilés.

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 x que j'ai créée pour l'utiliser dans ma bibliothèque.

19
Joel

Vous pouvez utiliser les fermetures de fonctions comme data dans des expressions plus grandes, comme dans cette méthode de détermination de la prise en charge du navigateur pour certains 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;
     })()
    }
8
kennebec

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

(function($) { ...code...})(jQuery);
7
Coronus
  1. Pour éviter les conflits avec d’autres méthodes/bibliothèques dans la même fenêtre,
  2. Éviter la portée globale, en faire une portée locale,
  3. Pour accélérer le débogage (portée locale),
  4. JavaScript a uniquement la portée des fonctions, il vous aidera donc à compiler les codes.
3
Vivek Mehta

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

(function() {
    'use strict';

    //Your code from here
})();
1
Neha Jain