Навіщо використовувати вирази з іменованими функціями?


93

У нас є два різні способи вираження функції в 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

Це сталося через занепокоєння щодо витоку інформації, яке виникало, коли нова функція проходила процес додавання; деталі у моїй відповіді на інше запитання тут .


1
Варто зазначити, що є принаймні два місця, в яких використання NFE все ще дає конкретні переваги: ​​по-перше, для функцій, призначених для використання в якості конструкторів через newоператор (надання таких назв функцій робить .constructorвластивість більш корисною під час налагодження, щоб зрозуміти, що за біса деякий об’єкт є екземпляром), а для функціональних літералів, що передаються безпосередньо у функцію без попереднього присвоєння властивості або змінної (наприклад setTimeout(function () {/*do stuff*/});). Навіть Chrome показує їх так, (anonymous function)якби ви не допомогли, назвавши їх.
Mark Amery

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 (Визначений вираз функції) МОЖЕ бути замінено 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.