¿Cuál es el propósito de envolver archivos Javascript completos en funciones anónimas como "(function () {...}) ()"?

584

Últimamente he estado leyendo mucho Javascript y me he dado cuenta de que todo el archivo está envuelto de la siguiente manera en los archivos .js que se van a importar.

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

¿Cuál es la razón para hacer esto en lugar de un simple conjunto de funciones de constructor?

Andrew Kou
fuente
66
Como imagino que esto será utilizado por mucha gente, no olvide el cierre;
dgh
55
Esta técnica se llama "IIFE", creo. Esto significa Expresión de función invocada inmediatamente en.wikipedia.org/wiki/Immediately-invoked_function_expression
Adrien Be

Respuestas:

786

Suele ser espacio de nombres (ver más adelante) y controlar la visibilidad de las funciones y / o variables de los miembros. Piense en ello como una definición de objeto. El nombre técnico para ello es una expresión de función invocada inmediatamente (IIFE). Los complementos de jQuery generalmente se escriben así.

En Javascript, puede anidar funciones. Entonces, lo siguiente es legal:

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

Ahora puede llamar outerFunction(), pero la visibilidad de innerFunction()está limitada al alcance de outerFunction(), lo que significa que es privado outerFunction(). Básicamente sigue el mismo principio que las variables en Javascript:

var globalVariable;

function someFunction() {
   var localVariable;
}

Correspondientemente:

function globalFunction() {

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

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

En el escenario anterior, puede llamar globalFunction()desde cualquier lugar, pero no puede llamar localFunction1o localFunction2.

Lo que estás haciendo cuando escribes (function() { ... })()es convertir el código dentro del primer conjunto de paréntesis en una función literal (lo que significa que todo el "objeto" es en realidad una función). Después de eso, está invocando la función (la final ()) que acaba de definir. Entonces, la principal ventaja de esto, como mencioné antes, es que puede tener métodos / funciones y propiedades privadas:

(function() {
   var private_var;

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

En el primer ejemplo, invocaría explícitamente globalFunctionpor nombre para ejecutarlo. Es decir, lo haría globalFunction()para ejecutarlo. Pero en el ejemplo anterior, no solo estás definiendo una función; lo estás definiendo e invocando de una vez. Esto significa que cuando se carga el archivo JavaScript, se ejecuta inmediatamente. Por supuesto, podrías hacer:

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

El comportamiento sería en gran medida el mismo, excepto por una diferencia significativa: evita contaminar el alcance global cuando usa un IIFE (como consecuencia, también significa que no puede invocar la función varias veces ya que no tiene un nombre, pero dado que esta función solo debe ejecutarse una vez que realmente no sea un problema).

Lo bueno de IIFE es que también puede definir cosas dentro y solo exponer las partes que desea al mundo exterior (un ejemplo de espacio de nombres para que pueda crear su propia biblioteca / complemento):

var myPlugin = (function() {
 var private_var;

 function private_function() {
 }

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

¡Ahora puede llamar myPlugin.public_function1(), pero no puede acceder private_function()! Muy parecido a una definición de clase. Para entender esto mejor, recomiendo los siguientes enlaces para leer más:

EDITAR

Olvidé mencionarlo. En esa final (), puedes pasar todo lo que quieras dentro. Por ejemplo, cuando crea complementos jQuery, pasa jQueryo le $gusta:

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

Entonces, lo que está haciendo aquí es definir una función que tome en un parámetro (llamada jQ, una variable local, y conocida solo por esa función). Luego, está invocando la función y pasando un parámetro (también llamado jQuery, pero este es del mundo exterior y una referencia al jQuery real). No hay una necesidad apremiante de hacer esto, pero hay algunas ventajas:

  • Puede redefinir un parámetro global y darle un nombre que tenga sentido en el ámbito local.
  • Hay una ligera ventaja de rendimiento, ya que es más rápido buscar cosas en el ámbito local en lugar de tener que subir la cadena del ámbito hacia el ámbito global.
  • Hay beneficios para la compresión (minificación).

Anteriormente describí cómo estas funciones se ejecutan automáticamente al inicio, pero si se ejecutan automáticamente, ¿quién pasa los argumentos? Esta técnica supone que todos los parámetros que necesita ya están definidos como variables globales. Entonces, si jQuery no estaba definido como una variable global, este ejemplo no funcionaría. Como puede suponer, una cosa que jquery.js hace durante su inicialización es definir una variable global 'jQuery', así como su variable global más famosa '$', que permite que este código funcione después de que se haya incluido jQuery.

Vivin Paliath
fuente
14
Muy bien, entiendo bien el espacio de nombres, pero he visto mucho de ese último ejemplo tuyo y no pude entender lo que la gente estaba tratando de lograr. Esto realmente aclara las cosas.
Andrew Kou
34
Impresionante publicación. Muchas gracias.
Darren
44
Creo agregar un punto y coma al principio y al final ';' completaría el ejemplo: de ;(function(jQ) { ... code ... })(jQuery);esta manera, si alguien dejara un punto y coma en su secuencia de comandos, no rompería la suya, especialmente si planea minificar y concatenar su secuencia de comandos con otra.
Taras Alenin
3
Buena publicación, me gusta el énfasis en las variables privadas. También me gusta la apertura del módulo-patrón / cierres (public_function1 y public_function2) y cómo pasa las variables, aunque salir un poco del alcance es una buena introducción. También agregué una respuesta, esta centrada en lo que supongo que son las raíces de la sintaxis y las diferencias entre la declaración de función frente a la expresión de función y lo que creo que es "solo una convención" frente a "la única forma de lograr este resultado".
Adrien Be
44
Gran publicación, creo que tal vez más sobre cómo pasar las variables a la función de ejecución automática es beneficioso. El contexto en la función de ejecución automática es limpio, sin datos. Puede pasar el contexto haciendo esto, (function (context) { ..... })(this)que luego le permite adjuntar todo lo que desee al contexto principal, exponiéndolo.
Callum Linington
79

En breve

Resumen

En su forma más simple, esta técnica tiene como objetivo envolver el código dentro del alcance de una función .

Ayuda a disminuir las posibilidades de:

  • chocando con otras aplicaciones / bibliotecas
  • Alcance contaminante superior (globalmente más probable)

Que no se detecta cuando el documento esté listo - no es una especie de document.onloadniwindow.onload

Es comúnmente conocido como un Immediately Invoked Function Expression (IIFE)o Self Executing Anonymous Function.

Código explicado

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

En el ejemplo anterior, cualquier variable definida en la función (es decir, declarada usando var) será "privada" y accesible dentro del alcance de la función SOLAMENTE (como lo expresa Vivin Paliath). En otras palabras, estas variables no son visibles / accesibles fuera de la función. Ver demostración en vivo .

Javascript tiene función de alcance. "Los parámetros y las variables definidas en una función no son visibles fuera de la función, y que una variable definida en cualquier lugar dentro de una función es visible en todas partes dentro de la función". (de "Javascript: las partes buenas").


Más detalles

Código alternativo

Al final, el código publicado antes también podría hacerse de la siguiente manera:

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

Ver demostración en vivo .


Las raices

Iteración 1

Un día, alguien probablemente pensó "debe haber una manera de evitar nombrar 'myMainFunction', ya que todo lo que queremos es ejecutarlo de inmediato".

Si vuelve a lo básico, descubre que:

  • expression: algo evaluando a un valor. es decir3+11/x
  • statement: línea (s) de código que hace algo PERO no evalúa a un valor. es decirif(){}

Del mismo modo, las expresiones de función evalúan a un valor. Y una consecuencia (¿supongo?) Es que se pueden invocar de inmediato:

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

Entonces nuestro ejemplo más complejo se convierte en:

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

Ver demostración en vivo .

Iteración 2

El siguiente paso es el pensamiento "¿por qué var myMainFunction =si ni siquiera lo usamos?".

La respuesta es simple: intente eliminar esto, como a continuación:

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

Ver demostración en vivo .

No funcionará porque "las declaraciones de funciones no son invocables" .

El truco es que al eliminar var myMainFunction =transformamos la expresión de la función en una declaración de función . Consulte los enlaces en "Recursos" para obtener más detalles al respecto.

La siguiente pregunta es "¿por qué no puedo mantenerlo como una expresión de función con otra cosa que no sea var myMainFunction =?

La respuesta es "usted puede", y en realidad hay muchas maneras en que podría hacer esto: agregando a +, a !, a -, o tal vez envolviendo un par de paréntesis (como ahora se hace por convención), y creo que hay más. Como ejemplo:

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

o

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

o

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

Entonces, una vez que se agrega la modificación relevante a lo que alguna vez fue nuestro "Código Alternativo", volvemos al mismo código exacto que el utilizado en el ejemplo de "Código Explicado"

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

Leer más sobre Expressions vs Statements:


Alcance desmitificador

Una cosa que uno podría preguntarse es "¿qué sucede cuando NO define la variable 'correctamente' dentro de la función, es decir, hace una asignación simple en su lugar?"

(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

Ver demostración en vivo .

Básicamente, si se asigna un valor a una variable que no se declaró en su alcance actual, entonces "se produce una búsqueda en la cadena del alcance hasta que encuentra la variable o alcanza el alcance global (en ese momento la creará)".

Cuando se encuentra en un entorno de navegador (frente a un entorno de servidor como nodejs), el windowobjeto define el alcance global . Por lo tanto podemos hacer window.myOtherFunction().

Mi consejo de "Buenas prácticas" sobre este tema es usar siempre varal definir cualquier cosa : ya sea un número, un objeto o una función, e incluso en el ámbito global. Esto hace que el código sea mucho más simple.

Nota:

  • javascript no tiene block scope(Actualización: variables locales de alcance de bloque agregadas en ES6 ).
  • javascript solo tiene function scope& global scope( windowalcance en un entorno de navegador)

Leer más sobre Javascript Scopes:


Recursos


Próximos pasos

Una vez que obtiene este IIFEconcepto, lo lleva a lo module patternque comúnmente se hace aprovechando este patrón IIFE. Que te diviertas :)

Adrien Be
fuente
Muy útil. ¡Muchas gracias!
Christoffer Helgelin Hald
Bien, prefiero la versión demo :)
Fabrizio Bertoglio
Esa gran explicación. ¡Gracias!
Vikram Khemlani
26

Javascript en un navegador solo tiene un par de alcances efectivos: alcance de la función y alcance global.

Si una variable no está en el alcance de la función, está en el alcance global. Y las variables globales son generalmente malas, por lo que esta es una construcción para mantener las variables de una biblioteca para sí mismo.

Gareth
fuente
1
Pero, ¿la función del constructor en sí misma no da margen para sus propias variables?
Andrew Kou
1
Sí, cada función definida en esta biblioteca podría definir sus propias variables locales, pero esto permite que las variables se compartan entre las funciones sin que se filtren fuera de la biblioteca
Gareth
@Gareth, por lo que esto permite variables "globales" dentro de un alcance (;
Francisco Presencia
2
@FranciscoPresencia "global dentro de un alcance" no es una frase útil, porque eso es básicamente lo que significa "alcance". El objetivo del alcance "global" es que es específicamente el alcance al que tienen acceso todos los demás ámbitos.
Gareth
19

Eso se llama un cierre. Básicamente sella el código dentro de la función para que otras bibliotecas no interfieran con él. Es similar a crear un espacio de nombres en idiomas compilados.

Ejemplo. Supongamos que escribo:

(function() {

    var x = 2;

    // do stuff with x

})();

Ahora otras bibliotecas no pueden acceder a la variable xque creé para usar en mi biblioteca.

Joel
fuente
77
Cuidado con tu terminología. El espacio de nombres implica que se puede acceder a las variables desde el exterior al direccionar el espacio de nombres (generalmente usando un prefijo). Si bien esto es posible en Javascript, eso no es lo que se demuestra aquí
Gareth
Estoy de acuerdo en que no es exactamente como un espacio de nombres, sin embargo, puede proporcionar una funcionalidad similar al devolver un objeto con propiedades que desea dar a conocer: (function(){ ... return { publicProp1: 'blah' }; })();. Obviamente no es perfectamente paralelo al espacio de nombres, pero puede ser útil pensarlo de esa manera.
Joel
en su ejemplo, x sigue siendo una variable privada ... A pesar de envolverlo en un IIFE. adelante e intente acceder a x fuera de la función, no puede ...
RayLoveless
Tu punto no es válido. Incluso en la siguiente función, otras bibliotecas no pueden acceder a x. function () {var x = 2}
RayLoveless
@RayLoveless estoy de acuerdo. No contradigo esa afirmación. De hecho, hice la misma afirmación que la última oración de esta respuesta.
Joel
8

También puede usar cierres de funciones como datos en expresiones más grandes, como en este método para determinar el soporte del navegador para algunos de los objetos 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
fuente
Lo que hace el !! ¿hacer?
1.21 gigavatios
!! convierte un valor a su representación booleana (verdadero / falso).
Liam
7

Además de mantener las variables locales, un uso muy útil es cuando se escribe una biblioteca usando una variable global, puede darle un nombre de variable más corto para usar dentro de la biblioteca. A menudo se usa para escribir complementos de jQuery, ya que jQuery le permite deshabilitar la variable $ que apunta a jQuery, usando jQuery.noConflict (). En caso de que esté deshabilitado, su código aún puede usar $ y no romperse si solo lo hace:

(function($) { ...code...})(jQuery);
Coronus
fuente
3
  1. Para evitar conflictos con otros métodos / bibliotecas en la misma ventana,
  2. Evite el alcance global, hágalo local,
  3. Para acelerar la depuración (ámbito local),
  4. JavaScript solo tiene un alcance de función, por lo que también ayudará en la compilación de códigos.
Vivek Mehta
fuente
1

También debemos usar 'use estricto' en la función de alcance para asegurarnos de que el código se ejecute en "modo estricto". Código de muestra que se muestra a continuación

(function() {
    'use strict';

    //Your code from here
})();
Neha Jain
fuente
¿Por qué debemos usar estricto?
nbro
Consulte este artículo: stackoverflow.com/questions/1335851/…
Neha Jain
¡Realmente no responde la pregunta!
Pritam Banerjee
Pritam, es una buena práctica de uso. Haga una investigación adecuada antes de rechazar cualquier respuesta
Neha Jain
1
'use estricto' salva a los malos programadores de sí mismos. Y dado que la mayoría de los programadores son malos programadores, ayuda a evitar que hagan cosas que definitivamente no deberían estar haciendo y terminen en un desastre de código que se hunde rápidamente.
MattE