Зачем использовать именованные функциональные выражения?

94

У нас есть два разных способа выполнения выражения функции в JavaScript:

Выражение именованной функции (NFE) :

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

Выражение анонимной функции :

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

И их обоих можно назвать с помощью boo();. Я действительно не понимаю, почему / когда мне следует использовать анонимные функции и когда я должен использовать выражения именованных функций. Какая между ними разница?

Афшин Мехрабани
источник

Ответы:

86

В случае выражения анонимной функции функция анонимна  - буквально, у нее нет имени. Переменная, которой вы ее назначаете, имеет имя, а функция - нет. (Обновление: это было правдой через ES5. Начиная с ES2015 [также известного как ES6], часто функция, созданная с помощью анонимного выражения, получает настоящее имя [но не автоматический идентификатор], читайте дальше ...)

Имена полезны. Имена можно увидеть в трассировке стека, стеках вызовов, списках точек останова и т. Д. Имена - это хорошая вещь ™.

(Раньше вам приходилось остерегаться именованных функциональных выражений в более старых версиях IE [IE8 и ниже], потому что они по ошибке создавали два полностью отдельных функциональных объекта в два совершенно разных момента [подробнее в моей статье блога Двойной дубль ]. Если вам нужно поддерживают IE8 [!!], вероятно, лучше придерживаться анонимных функциональных выражений или объявлений функций , но избегайте именованных функциональных выражений.)

Одним из ключевых моментов в выражении именованной функции является то, что оно создает идентификатор в области видимости с этим именем для функции в теле функции:

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

Однако, начиная с ES2015, многие «анонимные» функциональные выражения создают функции с именами, и этому предшествовали различные современные движки JavaScript, которые довольно умно выводили имена из контекста. В ES2015 ваше анонимное выражение функции приводит к функции с именем boo. Однако даже с семантикой ES2015 + автоматический идентификатор не создается:

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

Назначение имени функции выполняется с помощью SetFunctionName абстрактной операции используемой в различных операциях в спецификации.

Краткая версия - это в основном каждый раз, когда выражение анонимной функции появляется справа от чего-то вроде присваивания или инициализации, например:

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

(или это могло быть letили constскорее чем var) , или

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

или

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

(последние два на самом деле одно и то же) , получившаяся функция будет иметь имя ( booв примерах).

Есть важное и преднамеренное исключение: присвоение свойству существующего объекта:

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

Это произошло из-за опасений по поводу утечки информации, возникших, когда новая функция находилась в процессе добавления; подробности в моем ответе на другой вопрос здесь .

TJ Crowder
источник
1
Стоит отметить, что есть как минимум два места, в которых использование NFE по-прежнему дает конкретные преимущества: во-первых, для функций, предназначенных для использования в качестве конструкторов через newоператор (присвоение всем таким функциям имен делает .constructorсвойство более полезным во время отладки для выяснения того, что, черт возьми, некоторый объект является экземпляром), а также для функциональных литералов, переданных непосредственно в функцию без предварительного присвоения свойству или переменной (например, setTimeout(function () {/*do stuff*/});). Даже Chrome показывает их, как (anonymous function)если бы вы не помогли им, назвав их.
Марк Эмери
4
@MarkAmery: «Это все еще правда? Я ... пытался нажать CTRL-F для этих правил и не смог их найти» О да. :-) Он разбросан по всей спецификации, а не находится в одном месте, определяющем набор правил, просто ищите "setFunctionName". Я добавил небольшой набор ссылок выше, но в настоящее время он отображается примерно в 29 разных местах. Я был бы слегка удивлен, если бы ваш setTimeoutпример не взял имя из формального аргумента, объявленного для setTimeout, если бы он был. :-) Но да, NFE определенно полезны, если вы знаете, что не будете иметь дело со старыми браузерами, которые их хешируют.
TJ Crowder
24

Именование функций полезно, если им нужно ссылаться на самих себя (например, для рекурсивных вызовов). Действительно, если вы передаете буквальное функциональное выражение в качестве аргумента непосредственно другой функции, это функциональное выражение не может напрямую ссылаться на себя в строгом режиме ES5, если оно не названо.

Например, рассмотрим этот код:

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

Было бы невозможно написать этот код так чисто, если бы переданное выражение функции setTimeoutбыло анонимным; нам нужно было бы присвоить его переменной вместоsetTimeout вызовом. Таким образом, с именованным выражением функции становится немного короче и аккуратнее.

Исторически было возможно написать такой код даже с использованием выражения анонимной функции, используя arguments.callee...

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

... но arguments.calleeустарел и категорически запрещен в строгом режиме ES5. Следовательно, MDN советует:

Избегайте использования arguments.callee(), задавая функциональным выражениям имя или используйте объявление функции, в котором функция должна вызывать сама себя.

(курсив мой)

Марк Эмери
источник
3

Если функция указана как выражение функции, ей можно дать имя.

Он будет доступен только внутри функции (кроме IE8-).

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

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

Это имя предназначено для надежного рекурсивного вызова функции, даже если оно записано в другую переменную.

Кроме того, имя NFE (Named Function Expression) МОЖЕТ быть перезаписано Object.defineProperty(...)следующим способом:

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

test();

Примечание: это невозможно сделать с помощью Декларации функции. Это «специальное» внутреннее имя функции указывается только в синтаксисе выражения функции.

Римский
источник
2

Вы всегда должны использовать именованные функциональные выражения, поэтому:

  1. Вы можете использовать имя этой функции, когда вам нужна рекурсия.

  2. Анонимные функции не помогают при отладке, поскольку вы не можете увидеть имя функции, которая вызывает проблемы.

  3. Когда вы не называете функцию, позже становится труднее понять, что она делает. Если дать ему имя, его будет легче понять.

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

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

Здесь, например, поскольку строка имени используется в выражении функции, она не объявляется во внешней области видимости. В именованных функциональных выражениях имя функционального выражения заключено в его собственную область видимости.

Антеро Укконен
источник
1

Использование именованных функциональных выражений лучше, если вы хотите иметь возможность ссылаться на рассматриваемую функцию без необходимости полагаться на устаревшие функции, такие как arguments.callee.

Судхир Бастакоти
источник
3
Это скорее комментарий, чем ответ. Возможно, доработка была бы полезной
vsync