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

Resuelto Andrew Kou asked hace 14 años • 10 respuestas

He estado leyendo mucho Javascript últimamente y he notado que todo el archivo está empaquetado como se muestra a continuación 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 constructoras?

Andrew Kou avatar Mar 11 '10 08:03 Andrew Kou
Aceptado

Por lo general, es para crear espacios de nombres (ver más adelante) y controlar la visibilidad de funciones miembro y/o variables. Piense en ello como una definición de objeto. El nombre técnico es Expresión de función invocada inmediatamente (IIFE). Los complementos de jQuery generalmente se escriben así.

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

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

Ahora puedes llamar outerFunction(), pero la visibilidad de innerFunction()está limitada al alcance de outerFunction(), lo que significa que es privada para 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, puedes llamar globalFunction()desde cualquier lugar, pero no puedes llamar localFunction1o localFunction2.

Lo que estás haciendo cuando escribes (function() { ... })()es hacer que el código dentro del primer conjunto de paréntesis sea una función literal (lo que significa que todo el "objeto" es en realidad una función). Después de eso, estás autoinvocando la función (la final ()) que acabas de definir. Entonces, la principal ventaja de esto, como mencioné antes, es que puedes 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, bastaría globalFunction()con ejecutarlo. Pero en el ejemplo anterior, no sólo estás definiendo una función; lo estás definiendo e invocando de una sola vez. Esto significa que cuando se carga su archivo JavaScript, se ejecuta inmediatamente. Por supuesto, puedes hacer:

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

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

Lo bueno de los IIFE es que también puedes definir cosas dentro y exponer solo las partes que quieras al mundo exterior (un ejemplo de espacio de nombres para que puedas básicamente crear tu propia biblioteca/complemento):

var myPlugin = (function() {
 var private_var;

 function private_function() {
 }

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

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

  • Espacio de nombres en tu Javascript
  • Miembros privados en Javascript (por Douglas Crockford)

EDITAR

Olvidé mencionar. En esa final (), puedes pasar lo que quieras dentro. Por ejemplo, cuando crea complementos de jQuery, pasa jQueryo $me gusta así:

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

Entonces, lo que estás haciendo aquí es definir una función que toma un parámetro (llamado jQ, una variable local y conocida solo por esa función). Luego, autoinvocas la función y pasas un parámetro (también llamado jQuery, pero este es del mundo exterior y una referencia al propio jQuery). No hay una necesidad urgente de hacer esto, pero existen algunas ventajas:

  • Puede redefinir un parámetro global y darle un nombre que tenga sentido en el ámbito local.
  • Existe una ligera ventaja de rendimiento, ya que es más rápido buscar cosas en el ámbito local en lugar de tener que recorrer la cadena de ámbito hasta 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 estuviera definido como una variable global, este ejemplo no funcionaría. Como puedes adivinar, 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 avatar Mar 11 '2010 01:03 Vivin Paliath

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 superior contaminante (muy probablemente global)

No detecta cuando el documento está listo - no es algún tipo de document.onloadniwindow.onload

Se le conoce comúnmente como 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 SÓLO dentro del alcance de la función (como lo expresa Vivin Paliath). En otras palabras, estas variables no son visibles ni accesibles fuera de la función. Ver demostración en vivo .

Javascript tiene alcance de función. "Los parámetros y variables definidos en una función no son visibles fuera de la función, y 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 se podría hacer 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 lo único que queremos es ejecutarla inmediatamente".

Si vuelves a lo básico, descubrirás que:

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

De manera similar, las expresiones de funciones se evalúan como un valor. Y una consecuencia (¿supongo?) es que se pueden invocar inmediatamente:

 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 "¿¡para qué hacerlo var myMainFunction =si ni siquiera lo usamos!?".

La respuesta es simple: intente eliminar esto, como se muestra 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 mantenerla como una expresión de función con algo distinto a var myMainFunction =?

La respuesta es "puedes", y en realidad hay muchas maneras de hacer esto: agregando a +, a , !a -o tal vez envolviendo un par de paréntesis (como se hace ahora por convención), y creo que 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
  • ¿Qué hace el signo de exclamación antes de la función?
  • Signo más de JavaScript delante del nombre de la función

Entonces, una vez que se agrega la modificación relevante a lo que alguna vez fue nuestro "Código alternativo", volvemos exactamente al mismo código que se usó en el ejemplo del "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:

  • desarrollador.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators
  • desarrollador.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#Function_constructor_vs._function_declaration_vs._function_expression
  • Javascript: ¿diferencia entre una declaración y una expresión?
  • Expresión versus declaración

Demystifying Scopes

One thing one might wonder is "what happens when you do NOT define the variable 'properly' inside the function -- i.e. do a simple assignment instead?"

(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

See live demo.

Basically, if a variable that was not declared in its current scope is assigned a value, then "a look up the scope chain occurs until it finds the variable or hits the global scope (at which point it will create it)".

When in a browser environment (vs a server environment like nodejs) the global scope is defined by the window object. Hence we can do window.myOtherFunction().

My "Good practices" tip on this topic is to always use var when defining anything: whether it's a number, object or function, & even when in the global scope. This makes the code much simpler.

Note:

  • javascript does not have block scope (Update: block scope local variables added in ES6.)
  • javascript has only function scope & global scope (window scope in a browser environment)

Read more about Javascript Scopes:

  • What is the purpose of the var keyword and when to use it (or omit it)?
  • What is the scope of variables in JavaScript?

Resources

  • youtu.be/i_qE1iAmjFg?t=2m15s - Paul Irish presents the IIFE at min 2:15, do watch this!
  • developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions
  • Book: Javascript, the good parts - highly recommended
  • youtu.be/i_qE1iAmjFg?t=4m36s - Paul Irish presents the module pattern at 4:36

Next Steps

Once you get this IIFE concept, it leads to the module pattern, which is commonly done by leveraging this IIFE pattern. Have fun :)

Adriano avatar Nov 04 '2014 15:11 Adriano