¿Por qué utilizar expresiones de función con nombre?

93

Tenemos dos formas diferentes de hacer la expresión de funciones en JavaScript:

Expresión de función nombrada (NFE) :

var boo = function boo () {
  alert(1);
};

Expresión de función anónima :

var boo = function () {
  alert(1);
};

Y ambos se pueden llamar con boo();. Realmente no puedo ver por qué / cuándo debería usar funciones anónimas y cuándo debería usar Expresiones de función con nombre. ¿Qué diferencia hay entre ellos?

Afshin Mehrabani
fuente

Respuestas:

86

En el caso de la expresión de función anónima, la función es anónima  ; literalmente, no tiene nombre. La variable a la que lo está asignando tiene un nombre, pero la función no. (Actualización: Eso fue cierto a través de ES5. A partir de ES2015 [también conocido como ES6], a menudo una función creada con una expresión anónima obtiene un nombre verdadero [pero no un identificador automático], sigue leyendo ...)

Los nombres son útiles. Los nombres se pueden ver en seguimientos de pila, pilas de llamadas, listas de puntos de interrupción, etc. Los nombres son algo bueno ™.

(Solía ​​tener que tener cuidado con las expresiones de función con nombre en versiones anteriores de IE [IE8 y anteriores], porque crearon por error dos objetos de función completamente separados en dos momentos completamente diferentes [más en el artículo de mi blog Doble toma ]. Si es necesario admite IE8 [!!], probablemente sea mejor seguir con expresiones de función anónimas o declaraciones de función , pero evite las expresiones de función con nombre).

Una cosa clave acerca de una expresión de función nombrada es que crea un identificador dentro del alcance con ese nombre para la función dentro del cuerpo de la función:

var x = function example() {
    console.log(typeof example); // "function"
};
x();
console.log(typeof example);     // "undefined"

Sin embargo, a partir de ES2015, muchas expresiones de función "anónimas" crean funciones con nombres, y esto fue precedido por varios motores JavaScript modernos que son bastante inteligentes para inferir nombres a partir del contexto. En ES2015, su expresión de función anónima da como resultado una función con el nombre boo. Sin embargo, incluso con la semántica de ES2015 +, no se crea el identificador automático:

var obj = {
    x: function() {
       console.log(typeof x);   // "undefined"
       console.log(obj.x.name); // "x"
    },
    y: function y() {
       console.log(typeof y);   // "function"
       console.log(obj.y.name); // "y"
    }
};
obj.x();
obj.y();

La asignación del nombre de la función se realiza con la operación abstracta SetFunctionName utilizada en varias operaciones en la especificación.

La versión corta es básicamente cada vez que aparece una expresión de función anónima en el lado derecho de algo como una asignación o inicialización, como:

var boo = function() { /*...*/ };

(o podría ser leto en constlugar de var) , o

var obj = {
    boo: function() { /*...*/ }
};

o

doSomething({
    boo: function() { /*...*/ }
});

(esos dos últimos son realmente lo mismo) , la función resultante tendrá un nombre (boo , en los ejemplos).

Hay una excepción importante e intencionada: asignar una propiedad a un objeto existente:

obj.boo = function() { /*...*/ }; // <== Does not get a name

Esto se debió a las preocupaciones sobre la filtración de información que surgieron cuando la nueva función estaba en proceso de ser agregada; detalles en mi respuesta a otra pregunta aquí .

TJ Crowder
fuente
1
Vale la pena señalar que hay al menos dos lugares en los que el uso de NFE todavía ofrece ventajas concretas: en primer lugar, para las funciones destinadas a ser utilizadas como constructores a través del newoperador (dar todos los nombres de estas funciones hace que la .constructorpropiedad sea más útil durante la depuración para averiguar qué diablos algún objeto es una instancia de), y para los literales de función pasados ​​directamente a una función sin haber sido asignados primero a una propiedad o variable (por ejemplo setTimeout(function () {/*do stuff*/});). Incluso Chrome muestra estos como a (anonymous function)menos que lo ayudes nombrándolos.
Mark Amery
4
@MarkAmery: "¿esto sigue siendo cierto? Intenté presionar CTRL-F para estas reglas y no pude encontrarlas" Oh, sí. :-) Está esparcido por toda la especificación en lugar de estar en un solo lugar definiendo un conjunto de reglas, simplemente busque "setFunctionName". Agregué un pequeño subconjunto de enlaces arriba, pero aparece en ~ 29 lugares diferentes actualmente. Solo me sorprendería un poco si su setTimeoutejemplo no tomara el nombre del argumento formal declarado a favor setTimeout, si lo tuviera. :-) Pero sí, los NFE son definitivamente útiles si sabes que no tendrás que lidiar con navegadores antiguos que los convierten en un hash.
TJ Crowder
24

Las funciones de nombres son útiles si necesitan referenciarse a sí mismas (por ejemplo, para llamadas recursivas). De hecho, si está pasando una expresión de función literal como un argumento directamente a otra función, esa expresión de función no puede hacer referencia directamente a sí misma en el modo estricto de ES5 a menos que tenga un nombre.

Por ejemplo, considere este código:

setTimeout(function sayMoo() {
    alert('MOO');
    setTimeout(sayMoo, 1000);
}, 1000);

Sería imposible escribir este código de forma tan limpia si la expresión de función pasada setTimeoutfuera anónima; tendríamos que asignarlo a una variable antes de la setTimeoutllamada. De esta forma, con una expresión de función nombrada, es un poco más corta y ordenada.

Históricamente, era posible escribir código como este incluso usando una expresión de función anónima, explotando arguments.callee...

setTimeout(function () {
    alert('MOO');
    setTimeout(arguments.callee, 1000);
}, 1000);

... pero arguments.calleeestá obsoleto y totalmente prohibido en el modo estricto de ES5. Por lo tanto, MDN aconseja:

Evitar el uso arguments.callee()por cualquiera de las expresiones de función que da un nombre o usar una declaración de función en una función debe llamarse a sí misma.

(énfasis mío)

Mark Amery
fuente
3

Si una función se especifica como una expresión de función, se le puede dar un nombre.

Solo estará disponible dentro de la función (excepto IE8-).

var f = function sayHi(name) {
  alert( sayHi ); // Inside the function you can see the function code
};

alert( sayHi ); // (Error: undefined variable 'sayHi')

Este nombre está destinado a una llamada de función recursiva confiable, incluso si está escrito en otra variable.

Además, el nombre NFE (expresión de función con nombre) PUEDE sobrescribirse con el Object.defineProperty(...)método de la siguiente manera:

var test = function sayHi(name) {
  Object.defineProperty(test, 'name', { value: 'foo', configurable: true });
  alert( test.name ); // foo
};

test();

Nota: con la Declaración de función esto no se puede hacer. Este nombre de función interna "especial" se especifica sólo en la sintaxis de expresión de función.

romano
fuente
2

Siempre debe usar expresiones de función con nombre , por eso:

  1. Puede usar el nombre de esa función cuando necesite recursividad.

  2. Las funciones anónimas no ayudan al depurar, ya que no puede ver el nombre de la función que causa problemas.

  3. Cuando no nombra una función, más adelante es más difícil entender qué está haciendo. Darle un nombre hace que sea más fácil de entender.

var foo = function bar() {
    //some code...
};

foo();
bar(); // Error!

Aquí, por ejemplo, debido a que la barra de nombre se usa dentro de una expresión de función, no se declara en el ámbito externo. Con expresiones de función nombradas, el nombre de la expresión de función se incluye dentro de su propio ámbito.

Antero Ukkonen
fuente
1

El uso de expresiones de función con nombre es mejor, cuando desea poder hacer referencia a la función en cuestión sin tener que depender de características obsoletas como arguments.callee.

Sudhir Bastakoti
fuente
3
Eso es más un comentario que una respuesta. Quizás la elaboración sería beneficiosa
vsync