Explicar la sintaxis de la función anónima encapsulada

373

Resumen

¿Puede explicar el razonamiento detrás de la sintaxis de las funciones anónimas encapsuladas en JavaScript? ¿Por qué funciona esto, (function(){})();pero esto no function(){}();:?


Lo que yo sé

En JavaScript, uno crea una función con nombre como esta:

function twoPlusTwo(){
    alert(2 + 2);
}
twoPlusTwo();

También puede crear una función anónima y asignarla a una variable:

var twoPlusTwo = function(){
    alert(2 + 2);
};
twoPlusTwo();

Puede encapsular un bloque de código creando una función anónima, luego envolviéndola entre paréntesis y ejecutándola de inmediato:

(function(){
    alert(2 + 2);
})();

Esto es útil al crear scripts modulares, para evitar saturar el alcance actual, o el alcance global, con variables potencialmente conflictivas, como en el caso de los scripts de Greasemonkey, los complementos jQuery, etc.

Ahora entiendo por qué esto funciona. Los corchetes encierran el contenido y exponen solo el resultado (estoy seguro de que hay una mejor manera de describirlo), como con (2 + 2) === 4.


Lo que no entiendo

Pero no entiendo por qué esto no funciona igual de bien:

function(){
    alert(2 + 2);
}();

¿Me lo puedes explicar?

Premasagar
fuente
39
Creo que toda esta notación variada y formas de definir / configurar / llamar funciones es la parte más confusa de trabajar inicialmente con javascript. La gente tampoco tiende a hablar de ellos. No es un punto destacado en guías o blogs. Me sorprende porque es algo confuso para la mayoría de las personas, y las personas con fluidez en js también deben haber pasado por eso. Es como esta realidad tabú vacía de la que nunca se habla.
ahnbizcad
1
Lea también sobre el propósito de esta construcción , o verifique una explicación ( técnica ) (también aquí ). Para la colocación del paréntesis, vea esta pregunta sobre su ubicación .
Bergi
OT: Para aquellos que quieran saber dónde se usan mucho estas funciones anónimas, por favor lea adecuadamentegood.com/JavaScript-Module-Pattern-In-Depth.html
Alireza Fattahi
Este es un caso típico de expresiones de función invocadas inmediatamente (IIFE).
Aminu Kano

Respuestas:

410

No funciona porque se analiza como a FunctionDeclaration, y el identificador de nombre de las declaraciones de funciones es obligatorio .

Cuando lo rodea con paréntesis, se evalúa como a FunctionExpression, y las expresiones de función pueden nombrarse o no.

La gramática de un se FunctionDeclarationve así:

function Identifier ( FormalParameterListopt ) { FunctionBody }

Y FunctionExpressions:

function Identifieropt ( FormalParameterListopt ) { FunctionBody }

Como se puede ver el Identifier(Identificador opt ) token en FunctionExpressiones opcional, por lo tanto, podemos tener una expresión de función sin un nombre definido:

(function () {
    alert(2 + 2);
}());

O expresión de función con nombre :

(function foo() {
    alert(2 + 2);
}());

Los paréntesis (formalmente llamados Operador de agrupamiento ) pueden rodear solo expresiones, y se evalúa una expresión de función.

Las dos producciones gramaticales pueden ser ambiguas y pueden verse exactamente iguales, por ejemplo:

function foo () {} // FunctionDeclaration

0,function foo () {} // FunctionExpression

El analizador sabe si es a FunctionDeclarationo a FunctionExpression, según el contexto en el que aparece.

En el ejemplo anterior, el segundo es una expresión porque el operador de coma también puede manejar solo expresiones.

Por otro lado, FunctionDeclarations en realidad podría aparecer solo en lo que se llama " Program" código, que significa código fuera del alcance global y dentro FunctionBodyde otras funciones.

Deben evitarse las funciones dentro de los bloques, ya que pueden conducir a un comportamiento impredecible, por ejemplo:

if (true) {
  function foo() {
    alert('true');
  }
} else {
  function foo() {
    alert('false!');
  }
}

foo(); // true? false? why?

El código anterior en realidad debería producir un SyntaxError, ya que un Blocksolo puede contener declaraciones (y la Especificación ECMAScript no define ninguna declaración de función), pero la mayoría de las implementaciones son tolerantes, y simplemente tomarán la segunda función, la que alerta 'false!'.

Las implementaciones de Mozilla -Rhino, SpiderMonkey, - tienen un comportamiento diferente. Su gramática contiene una declaración de función no estándar , lo que significa que la función se evaluará en tiempo de ejecución , no en tiempo de análisis, como sucede con FunctionDeclarations. En esas implementaciones obtendremos la primera función definida.


Las funciones se pueden declarar de diferentes maneras, compare lo siguiente :

1- Una función definida con el constructor Función asignado a la variable multiplicar :

var multiply = new Function("x", "y", "return x * y;");

2- Una declaración de función de una función llamada multiplicar :

function multiply(x, y) {
    return x * y;
}

3- Una expresión de función asignada a la variable multiplica :

var multiply = function (x, y) {
    return x * y;
};

4- Una expresión de función con nombre func_name , asignada a la variable multiplica :

var multiply = function func_name(x, y) {
    return x * y;
};
CMS
fuente
2
La respuesta de CMS es correcta. Para obtener una excelente explicación detallada de las declaraciones y expresiones de funciones, consulte este artículo de kangax .
Tim Down
Esta es una respuesta genial. Parece estar íntimamente relacionado con la forma en que se analiza el texto fuente y la estructura del BNF. en su ejemplo 3, ¿debería decir que es una expresión de función porque sigue un signo igual, mientras que esa forma es una declaración / declaración de función cuando aparece en una línea por sí misma? Me pregunto cuál sería el propósito de eso: ¿se interpreta simplemente como una declaración de función con nombre, pero sin un nombre? ¿Para qué sirve eso si no lo está asignando a una variable, nombrándola o llamándola?
Breton
1
Ajá. Muy útil. Gracias CMS Esta parte de los documentos de Mozilla a los que se vinculó es especialmente esclarecedora: developer.mozilla.org/En/Core_JavaScript_1.5_Reference/…
Premasagar el
1
+1, aunque tenía el corchete de cierre en la posición incorrecta en la expresión de la función :-)
NickFitz
1
@GovindRai, No. Las declaraciones de funciones se manejan en tiempo de compilación y una declaración de función duplicada anula la declaración anterior. En tiempo de ejecución, la declaración de función ya está disponible y, en este caso, la que está disponible es la que alerta "falso". Para obtener más información, lea no sabe JS
Saurabh Misra
50

A pesar de que esta es una vieja pregunta y respuesta, discute un tema que hasta el día de hoy arroja a muchos desarrolladores a un ciclo. No puedo contar la cantidad de candidatos de desarrolladores de JavaScript que he entrevistado que no pudieron decirme la diferencia entre una declaración de función y una expresión de función y que no tenían idea de qué es una expresión de función invocada de inmediato.

Sin embargo, me gustaría mencionar que una cosa muy importante es que el fragmento de código de Premasagar no funcionaría incluso si le hubiera dado un identificador de nombre.

function someName() {
    alert(2 + 2);
}();

La razón por la que esto no funcionaría es porque el motor de JavaScript interpreta esto como una declaración de función seguida de un operador de agrupación completamente no relacionado que no contiene expresión, y los operadores de agrupación deben contener una expresión. Según JavaScript, el fragmento de código anterior es equivalente al siguiente.

function someName() {
    alert(2 + 2);
}

();

Otra cosa que me gustaría señalar que puede ser útil para algunas personas es que cualquier identificador de nombre que proporcione para una expresión de función es prácticamente inútil en el contexto del código, excepto desde dentro de la definición de la función en sí.

var a = function b() {
    // do something
};
a(); // works
b(); // doesn't work

var c = function d() {
    window.setTimeout(d, 1000); // works
};

Por supuesto, usar identificadores de nombre con las definiciones de funciones siempre es útil cuando se trata de depurar código, pero eso es algo completamente diferente ... :-)

natlee75
fuente
17

Grandes respuestas ya se han publicado. Pero quiero señalar que las declaraciones de función devuelven un registro de finalización vacío:

14.1.20 - Semántica de tiempo de ejecución: evaluación

FunctionDeclaration : function BindingIdentifier ( FormalParameters ) { FunctionBody }

  1. Devuelve NormalCompletion (vacío).

Este hecho no es fácil de observar, porque la mayoría de las formas de intentar obtener el valor devuelto convertirá la declaración de función en una expresión de función. Sin embargo, lo evalmuestra:

var r = eval("function f(){}");
console.log(r); // undefined

Llamar a un registro de finalización vacío no tiene sentido. Por eso function f(){}()no puede funcionar. De hecho, el motor JS ni siquiera intenta llamarlo, los paréntesis se consideran parte de otra declaración.

Pero si ajusta la función entre paréntesis, se convierte en una expresión de función:

var r = eval("(function f(){})");
console.log(r); // function f(){}

Las expresiones de función devuelven un objeto de función. Y por lo tanto se le puede llamar: (function f(){})().

Oriol
fuente
Es una pena que esta respuesta se pase por alto. Si bien no es tan completo como la respuesta aceptada, proporciona información adicional muy útil y merece más votos
Avrohom Yisroel
10

En javascript, esto se denomina expresión de función invocada inmediatamente (IIFE) .

Para que sea una expresión de función, debe:

  1. encerrarlo usando ()

  2. colocar un operador vacío antes

  3. asignarlo a una variable.

De lo contrario, se tratará como definición de función y no podrá llamarlo / invocarlo al mismo tiempo de la siguiente manera:

 function (arg1) { console.log(arg1) }(); 

Lo anterior te dará un error. Porque solo puede invocar una expresión de función inmediatamente.

Esto se puede lograr de dos maneras: Forma 1:

(function(arg1, arg2){
//some code
})(var1, var2);

Camino 2:

(function(arg1, arg2){
//some code
}(var1, var2));

Camino 3:

void function(arg1, arg2){
//some code
}(var1, var2);

camino 4:

  var ll = function (arg1, arg2) {
      console.log(arg1, arg2);
  }(var1, var2);

Todo lo anterior invocará inmediatamente la expresión de la función.

asmmahmud
fuente
3

Solo tengo otro pequeño comentario. Su código funcionará con un pequeño cambio:

var x = function(){
    alert(2 + 2);
}();

Uso la sintaxis anterior en lugar de la versión más extendida:

var module = (function(){
    alert(2 + 2);
})();

porque no logré que la sangría funcionara correctamente para los archivos javascript en vim. Parece que a vim no le gustan las llaves dentro de paréntesis abiertos.

Andrei Bozantan
fuente
Entonces, ¿por qué funciona esta sintaxis cuando asigna el resultado ejecutado a una variable, pero no independiente?
paislee
1
@paislee: porque el motor de JavaScript interpreta cualquier declaración de JavaScript válida que comience con la functionpalabra clave como una declaración de función, en cuyo caso el seguimiento ()se interpreta como un operador de agrupación que, de acuerdo con las reglas de sintaxis de JavaScript, solo puede y debe contener una expresión de JavaScript.
natlee75
1
@bosonix: su sintaxis preferida funciona bien, pero es una buena idea usar la "versión más difundida" a la que hizo referencia o la variante ()encerrada dentro del operador de agrupación (la que recomienda Douglas Crockford) para mantener la coherencia: es es común usar IIFE sin asignarlos a una variable, y es fácil olvidar incluir esos paréntesis de ajuste si no los usa de manera consistente.
natlee75
0

Quizás la respuesta más corta sería que

function() { alert( 2 + 2 ); }

es una función literal que define una función (anónima). No se espera un par adicional (), que se interpreta como una expresión, a nivel superior, solo literales.

(function() { alert( 2 + 2 ); })();

está en una declaración de expresión que invoca una función anónima.

theking2
fuente
0
(function(){
     alert(2 + 2);
 })();

Lo anterior es una sintaxis válida porque todo lo que se pasa dentro del paréntesis se considera como expresión de función.

function(){
    alert(2 + 2);
}();

Lo anterior no es una sintaxis válida. Debido a que el analizador de sintaxis de script java busca el nombre de la función después de la palabra clave de la función, ya que no encuentra nada, arroja un error.

Vithy
fuente
2
Si bien su respuesta no es incorrecta, la respuesta aceptada ya cubre todo esto en el departamento.
Hasta Arnold el
0

También puedes usarlo como:

! function() { console.log('yeah') }()

o

!! function() { console.log('yeah') }()

!- negación op convierte la definición fn en expresión fn, por lo tanto, puede invocarla inmediatamente con (). Lo mismo que usar 0,fn defovoid fn def

Csaba K.
fuente
-1

Se pueden usar con parámetros-argumentos como

var x = 3; 
var y = 4;

(function(a,b){alert(a + b)})(x,y)

resultaría como 7

Judas
fuente
-1

Esos paréntesis adicionales crean funciones anónimas adicionales entre el espacio de nombres global y la función anónima que contiene el código. Y en Javascript, las funciones declaradas dentro de otras funciones solo pueden acceder al espacio de nombres de la función principal que las contiene. Como hay un objeto adicional (función anónima) entre el alcance global y el alcance del código real, no se conserva.

Jarkko Hietala
fuente