Po co używać nazwanych wyrażeń funkcyjnych?

94

Istnieją dwa różne sposoby wykonywania wyrażeń funkcyjnych w JavaScript:

Nazwane wyrażenie funkcyjne (NFE) :

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

Anonimowe wyrażenie funkcyjne :

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

I oba z nich można wywołać za pomocą boo();. Naprawdę nie rozumiem, dlaczego / kiedy powinienem używać funkcji anonimowych i kiedy powinienem używać nazwanych wyrażeń funkcyjnych. Jaka jest między nimi różnica?

Afshin Mehrabani
źródło

Odpowiedzi:

86

W przypadku anonimowego wyrażenia funkcyjnego funkcja jest anonimowa  - dosłownie nie ma nazwy. Zmienna, do której jest przypisywana, ma nazwę, ale funkcja nie. (Aktualizacja: tak było w ES5. Od ES2015 [aka ES6] często funkcja utworzona za pomocą anonimowego wyrażenia otrzymuje prawdziwą nazwę [ale nie automatyczny identyfikator], czytaj dalej ...)

Nazwy są przydatne. Nazwy można zobaczyć w śladach stosu, stosach wywołań, listach punktów przerwania, itp. Nazwy są dobrą rzeczą ™.

(Kiedyś trzeba było uważać na nazwane wyrażenia funkcyjne w starszych wersjach IE [IE8 i poniżej], ponieważ omyłkowo utworzyły dwa całkowicie oddzielne obiekty funkcyjne w dwóch zupełnie różnych momentach [więcej w moim artykule na blogu Double take ]. Jeśli potrzebujesz obsługują IE8 [!!], prawdopodobnie najlepiej jest trzymać się anonimowych wyrażeń funkcji lub deklaracji funkcji , ale unikaj nazwanych wyrażeń funkcji.)

Jedną z kluczowych cech nazwanego wyrażenia funkcji jest to, że tworzy ono identyfikator w zakresie o tej nazwie dla funkcji w treści funkcji:

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

Jednak od ES2015 wiele „anonimowych” wyrażeń funkcyjnych tworzy funkcje z nazwami, a było to poprzedzone przez różne nowoczesne silniki JavaScript, które dość sprytnie kojarzyły nazwy z kontekstem. W ES2015 anonimowe wyrażenie funkcji daje w wyniku funkcję o nazwie boo. Jednak nawet z semantyką ES2015 +, automatyczny identyfikator nie jest tworzony:

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

Przypisanie nazwy funkcji odbywa się za pomocą abstrakcyjnej operacji SetFunctionName używanej w różnych operacjach w specyfikacji.

Krótka wersja jest w zasadzie za każdym razem, gdy anonimowe wyrażenie funkcji pojawia się po prawej stronie czegoś takiego jak przypisanie lub inicjalizacja, na przykład:

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

(lub może być, leta constraczej niż var) , lub

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

lub

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

(te dwa ostatnie to tak naprawdę to samo) , wynikowa funkcja będzie miała nazwę ( boow przykładach).

Istnieje ważny i celowy wyjątek: przypisanie do właściwości istniejącego obiektu:

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

Wynikało to z obaw związanych z wyciekiem informacji, które pojawiły się podczas dodawania nowej funkcji; szczegóły w mojej odpowiedzi na inne pytanie tutaj .

TJ Crowder
źródło
1
Warto zauważyć, że istnieją co najmniej dwa miejsca, w których używanie NFE nadal daje konkretne korzyści: po pierwsze, dla funkcji, które mają być używane jako konstruktory za pośrednictwem newoperatora (nadanie nazw wszystkim takim funkcjom sprawia, że .constructorwłaściwość jest bardziej użyteczna podczas debugowania, aby dowiedzieć się, co do cholery jakiś obiekt jest instancją), a dla literałów funkcji przekazywanych bezpośrednio do funkcji bez wcześniejszego przypisania ich właściwości lub zmiennej (np setTimeout(function () {/*do stuff*/});.). Nawet Chrome pokazuje je jako, (anonymous function)chyba że pomożesz im, nazywając je.
Mark Amery,
4
@MarkAmery: "Czy to nadal prawda? Ja ... próbowałem użyć CTRL-F dla tych reguł i nie mogłem ich znaleźć" O tak. :-) Jest porozrzucany po specyfikacji, a nie w jednym miejscu definiującym zestaw reguł, po prostu wyszukaj „setFunctionName”. Dodałem powyżej mały podzbiór linków, ale obecnie pojawia się on w ~ 29 różnych miejscach. Byłbym tylko lekko zdziwiony, gdyby twój setTimeoutprzykład nie wziął nazwy z zadeklarowanego argumentu formalnego setTimeout, gdyby taki miał. :-) Ale tak, NFE są zdecydowanie przydatne, jeśli wiesz, że nie będziesz mieć do czynienia ze starymi przeglądarkami, które robią z nich skrót.
TJ Crowder
24

Nazywanie funkcji jest przydatne, jeśli muszą odwoływać się do siebie (np. W przypadku wywołań rekurencyjnych). Rzeczywiście, jeśli przekazujesz dosłowne wyrażenie funkcji jako argument bezpośrednio do innej funkcji, to wyrażenie funkcyjne nie może bezpośrednio odwoływać się do siebie w trybie ścisłym ES5, chyba że zostanie nazwane.

Na przykład rozważ ten kod:

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

Niemożliwe byłoby napisanie tego kodu tak czysto, gdyby przekazane wyrażenie funkcyjne setTimeoutbyło anonimowe; zamiast tego musielibyśmy przypisać go do zmiennej przed setTimeoutwywołaniem. W ten sposób, z nazwanym wyrażeniem funkcyjnym, jest nieco krótszy i schludniejszy.

Historycznie było możliwe napisanie takiego kodu nawet przy użyciu anonimowego wyrażenia funkcyjnego, wykorzystując arguments.callee...

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

... ale arguments.calleejest przestarzały i jest całkowicie zabroniony w trybie ścisłym ES5. Dlatego MDN radzi:

Unikaj używania arguments.callee()przez nadanie wyrażeniom funkcji nazwy lub użyj deklaracji funkcji, w której funkcja musi wywołać samą siebie.

(podkreślenie moje)

Mark Amery
źródło
3

Jeśli funkcja jest określona jako wyrażenie funkcji, można nadać jej nazwę.

Będzie dostępny tylko wewnątrz funkcji (z wyjątkiem IE8-).

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

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

Ta nazwa jest przeznaczona dla niezawodnego rekurencyjnego wywołania funkcji, nawet jeśli jest zapisana w innej zmiennej.

Ponadto nazwa NFE (Named Function Expression) MOŻE zostać nadpisana Object.defineProperty(...)następującą metodą:

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

test();

Uwaga: nie można tego zrobić w przypadku deklaracji funkcji. Ta „specjalna” nazwa funkcji wewnętrznej jest określona tylko w składni wyrażenia funkcji.

rzymski
źródło
2

Powinieneś zawsze używać nazwanych wyrażeń funkcyjnych, dlatego:

  1. Możesz użyć nazwy tej funkcji, gdy potrzebujesz rekursji.

  2. Funkcje anonimowe nie pomagają podczas debugowania, ponieważ nie widać nazwy funkcji, która powoduje problemy.

  3. Kiedy nie nazwiesz funkcji, później trudniej będzie zrozumieć, co ona robi. Nadanie mu nazwy ułatwia zrozumienie.

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

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

Tutaj, na przykład, ponieważ pasek nazwy jest używany w wyrażeniu funkcyjnym, nie jest deklarowany w zewnętrznym zakresie. W przypadku nazwanych wyrażeń funkcyjnych nazwa wyrażenia funkcyjnego jest ujęta w jego własnym zakresie.

Antero Ukkonen
źródło
1

Używanie nazwanych wyrażeń funkcyjnych jest lepsze, gdy chcesz mieć możliwość odniesienia się do danej funkcji bez konieczności polegania na przestarzałych funkcjach, takich jak arguments.callee.

Sudhir Bastakoti
źródło
3
To bardziej komentarz niż odpowiedź. Być może opracowanie byłoby korzystne
vsync