Explicar la sintaxis de la función anónima encapsulada.
Resumen
¿Puedes explicar el razonamiento detrás de la sintaxis de funciones anónimas encapsuladas en JavaScript? ¿Por qué esto funciona: (function(){})();
pero esto no function(){}();
:?
Lo que yo sé
En JavaScript, se crea una función con nombre como esta:
function twoPlusTwo(){
alert(2 + 2);
}
twoPlusTwo();
También puedes 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 corchetes y ejecutándola inmediatamente:
(function(){
alert(2 + 2);
})();
Esto es útil al crear scripts modularizados, 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 de 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);
}();
¿Puedes explicarme eso?
No funciona porque se está analizando como FunctionDeclaration
y el identificador de nombre de las declaraciones de función es obligatorio .
Cuando lo rodea entre paréntesis, se evalúa como FunctionExpression
y las expresiones de función pueden tener nombre o no.
La gramática de a FunctionDeclaration
se ve así:
function Identifier ( FormalParameterListopt ) { FunctionBody }
Y FunctionExpression
s:
function Identifieropt ( FormalParameterListopt ) { FunctionBody }
Como puede ver, el token Identifier
(Identificador optFunctionExpression
) es 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 nombrada :
(function foo() {
alert(2 + 2);
}());
Los paréntesis (formalmente llamados operador de agrupación ) solo pueden rodear 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 FunctionDeclaration
o a FunctionExpression
, dependiendo del contexto donde aparece.
En el ejemplo anterior, la segunda es una expresión porque el operador de coma también puede manejar sólo expresiones.
Por otro lado, FunctionDeclaration
s podría aparecer sólo en lo que se llama Program
código " ", es decir, código fuera del alcance global y dentro de FunctionBody
otras funciones.
Se deben evitar las funciones dentro de bloques, porque pueden provocar 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 a Block
solo 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 FunctionDeclaration
s. 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 de funciones 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 multiplicar :
var multiply = function (x, y) {
return x * y;
};
4- Una expresión de función nombrada func_name , asignada a la variable multiplicar :
var multiply = function func_name(x, y) {
return x * y;
};
Aunque esta es una pregunta y respuesta antiguas, analiza un tema que hasta el día de hoy desconcierta a muchos desarrolladores. No puedo contar la cantidad de candidatos a desarrolladores de JavaScript que entrevisté y que no podían 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 inmediatamente.
Sin embargo, me gustaría mencionar una cosa muy importante: 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 que el motor JavaScript interpreta esto como una declaración de función seguida de un operador de agrupación completamente no relacionado que no contiene ninguna 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 de alguna utilidad 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 dentro de la definición de la función misma.
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 nombres con las definiciones de funciones siempre es útil cuando se trata de depurar código, pero eso es algo completamente distinto... :-)
Ya se han publicado excelentes respuestas. Pero quiero señalar que las declaraciones de funciones devuelven un registro de finalización vacío:
14.1.20 - Semántica en tiempo de ejecución: Evaluación
Declaración de función :
function
BindingIdentifier(
FormalParameters)
{
FunctionBody}
- 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án la declaración de función en una expresión de función. Sin embargo, eval
lo muestra:
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 puedo trabajar. De hecho, el motor JS ni siquiera intenta llamarlo, los paréntesis se consideran parte de otra declaración.
Pero si envuelve 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 eso puedes llamarlo: (function f(){})()
.
Sólo tengo otro pequeño comentario. Su código funcionará con un pequeño cambio:
var x = function(){
alert(2 + 2);
}();
Utilizo 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 archivos javascript en vim. Parece que a vim no le gustan las llaves dentro del paréntesis abierto.