Perché usare espressioni di funzione con nome?


92

Abbiamo due modi diversi per eseguire l'espressione della funzione in JavaScript:

Espressione di funzione denominata (NFE) :

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

Espressione di funzione anonima :

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

Ed entrambi possono essere chiamati con boo();. Non riesco davvero a capire perché / quando dovrei usare funzioni anonime e quando dovrei usare espressioni di funzione denominate. Che differenza c'è tra loro?


Risposte:


85

Nel caso dell'espressione di funzione anonima, la funzione è anonima  - letteralmente, non ha nome. La variabile a cui la stai assegnando ha un nome, ma la funzione no. (Aggiornamento: questo era vero attraverso ES5. A partire da ES2015 [aka ES6], spesso una funzione creata con un'espressione anonima ottiene un vero nome [ma non un identificatore automatico], continua a leggere ...)

I nomi sono utili. I nomi possono essere visualizzati nelle tracce dello stack, negli stack delle chiamate, negli elenchi di punti di interruzione, ecc. I nomi sono una buona cosa ™.

(Dovevi fare attenzione alle espressioni di funzione con nome nelle versioni precedenti di IE [IE8 e inferiori], perché creavano per errore due oggetti funzione completamente separati in due momenti completamente diversi [più nel mio articolo del blog Double take ]. Se necessario supporta IE8 [!!], probabilmente è meglio attenersi a espressioni di funzioni anonime o dichiarazioni di funzioni , ma evitare espressioni di funzioni con nome.)

Una cosa fondamentale di un'espressione di funzione denominata è che crea un identificatore nell'ambito con quel nome per la funzione all'interno del corpo della funzione:

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

A partire da ES2015, tuttavia, molte espressioni di funzioni "anonime" creano funzioni con nomi, e questo è stato preceduto da vari motori JavaScript moderni che erano piuttosto intelligenti nell'inferenza di nomi dal contesto. In ES2015, l'espressione della funzione anonima risulta in una funzione con il nome boo. Tuttavia, anche con la semantica ES2015 +, l'identificatore automatico non viene creato:

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();

L'assegnazione del nome della funzione viene eseguita con l' operazione astratta SetFunctionName utilizzata in varie operazioni nelle specifiche.

La versione breve è fondamentalmente ogni volta che un'espressione di funzione anonima appare sul lato destro di qualcosa come un assegnamento o un'inizializzazione, come:

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

(o potrebbe essere leto constpiuttosto che var) , o

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

o

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

(questi ultimi due sono davvero la stessa cosa) , la funzione risultante avrà un nome ( boo, negli esempi).

C'è un'eccezione importante e intenzionale: l'assegnazione a una proprietà su un oggetto esistente:

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

Ciò era dovuto alle preoccupazioni sulla fuga di informazioni sollevate durante il processo di aggiunta della nuova funzionalità; dettagli nella mia risposta a un'altra domanda qui .


1
Vale la pena notare che ci sono almeno due punti in cui l'uso di NFE offre ancora vantaggi concreti: in primo luogo, per le funzioni destinate ad essere utilizzate come costruttori tramite l' newoperatore (dare a tutti questi nomi di funzioni rende la .constructorproprietà più utile durante il debug per capire cosa diavolo qualche oggetto è un'istanza di), e per i valori letterali di funzione passati direttamente in una funzione senza prima essere assegnati a una proprietà o variabile (ad esempio setTimeout(function () {/*do stuff*/});). Anche Chrome mostra questi come a (anonymous function)meno che tu non lo aiuti nominandoli.
Mark Amery

4
@MarkAmery: "è ancora vero? Ho ... provato a CTRL-F per queste regole e non sono riuscito a trovarle" Oh sì. :-) È disseminato in tutte le specifiche piuttosto che essere in un unico posto che definisce un insieme di regole, cerca semplicemente "setFunctionName". Ho aggiunto un piccolo sottoinsieme di collegamenti sopra, ma al momento appare in ~ 29 posti diversi. Sarei solo leggermente sorpreso se il tuo setTimeoutesempio non prendesse il nome dall'argomento formale dichiarato per setTimeout, se ne avesse uno. :-) Ma sì, gli NFE sono decisamente utili se sai che non avrai a che fare con vecchi browser che ne fanno un hash.
TJ Crowder

24

Le funzioni di denominazione sono utili se devono fare riferimento a se stesse (ad esempio per chiamate ricorsive). Infatti, se si passa un'espressione di funzione letterale come argomento direttamente a un'altra funzione, tale espressione di funzione non può fare riferimento direttamente a se stessa in modalità rigorosa ES5 a meno che non sia denominata.

Ad esempio, considera questo codice:

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

Sarebbe impossibile scrivere questo codice in modo così pulito se l'espressione della funzione passata setTimeoutfosse anonima; dovremmo assegnarlo a una variabile invece prima della setTimeoutchiamata. In questo modo, con un'espressione di funzione denominata, è leggermente più breve e più ordinato.

Storicamente era possibile scrivere codice come questo anche utilizzando un'espressione di funzione anonima, sfruttando arguments.callee...

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

... ma arguments.calleeè deprecato ed è assolutamente vietato in modalità rigorosa ES5. Quindi MDN consiglia:

Evitare l'uso arguments.callee()da entrambi espressioni di funzione dando un nome o utilizzare una dichiarazione di funzione in cui una funzione deve chiamare se stesso.

(enfasi mia)


3

Se una funzione viene specificata come espressione di funzione, è possibile assegnarle un nome.

Sarà disponibile solo all'interno della funzione (tranne IE8-).

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

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

Questo nome è inteso per una chiamata di funzione ricorsiva affidabile, anche se è scritto su un'altra variabile.

Inoltre, il nome NFE (Named Function Expression) PU essere sovrascritto con il Object.defineProperty(...)metodo come segue:

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

test();

Nota: che con la dichiarazione di funzione questo non può essere fatto. Questo nome di funzione interna "speciale" è specificato solo nella sintassi dell'espressione di funzione.


2

Dovresti sempre usare espressioni di funzione con nome , ecco perché:

  1. È possibile utilizzare il nome di quella funzione quando è necessaria la ricorsione.

  2. Le funzioni anonime non aiutano durante il debug poiché non è possibile vedere il nome della funzione che causa problemi.

  3. Quando non si nomina una funzione, in seguito è più difficile capire cosa sta facendo. Dare un nome lo rende più facile da capire.

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

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

Qui, ad esempio, poiché la barra del nome viene utilizzata all'interno di un'espressione di funzione, non viene dichiarata nell'ambito esterno. Con le espressioni di funzione denominate, il nome dell'espressione di funzione è racchiuso nel proprio ambito.


1

È preferibile utilizzare espressioni di funzione denominate quando si desidera poter fare riferimento alla funzione in questione senza dover fare affidamento su funzionalità deprecate come arguments.callee.


3
È più un commento che una risposta. Forse l'elaborazione sarebbe vantaggiosa
vsync
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.