Var functionName = function () {} vs function functionName () {}

Нещодавно я почав підтримувати код JavaScript іншого користувача. Я виправляю помилки, додаю функції, а також намагаюся привести в порядок код і зробити його більш послідовним.

Попередній розробник використовує два способи оголошення функцій, і я не можу розібратися, якщо є причина цього чи ні.

Двома способами є:

 var functionOne = function() { // Some code }; 
 function functionTwo() { // Some code } 

Які причини використання цих двох різних методів і які плюси і мінуси кожного з них? Чи є щось, що можна зробити за допомогою одного методу, який не можна зробити з іншим?

6296
03 дек. заданий Richard Garside 03 дек. 2008-12-03 14:31 '08 о 14:31 2008-12-03 14:31
@ 37 відповідей
  • 1
  • 2

Різниця в тому, що functionOne є виразом функції і тому визначається тільки тоді, коли ця лінія досягнута, тоді як functionTwo є оголошенням функції і визначається, як тільки виконується її оточує функція або сценарій (через підйому ).

Наприклад, вираз функції:

 // Outputs: "Hello!" functionTwo(); function functionTwo() { console.log("Hello!"); } 

Це також означає, що ви не можете умовно визначати функції за допомогою оголошень функцій:

 if (test) { // Error or misbehavior function functionThree() { doSomething(); } } 

Вищеописане фактично визначає functionThree незалежно від test значення - якщо тільки не use strict дію, і в цьому випадку він просто викликає помилку.

4669
03 дек. відповідь дан Greg 03 дек. 2008-12-03 14:37 '08 о 14:37 2008-12-03 14:37

Спочатку я хочу виправити Грега: function abc(){} теж обмежений; - ім'я abc визначається в області, де це визначення зустрічається. приклад:

 function xyz(){ function abc(){}; // abc is defined here... } // ...but not here 

По-друге, можна комбінувати обидва стилю:

 var xyz = function abc(){}; 

xyz буде визначатися як зазвичай, abc - undefined у всіх браузерах, але Internet Explorer - не надійтесь на його визначення. Але він буде визначений всередині його тіла:

 var xyz = function abc(){ // xyz is visible here // abc is visible here } // xyz is visible here // abc is undefined here 

Якщо ви хочете використовувати псевдоніми в усіх браузерах, використовуйте цей вид оголошення:

 function abc(){}; var xyz = abc; 

В цьому випадку обидва xyz і abc є аліасами одного і того ж об'єкта:

 console.log(xyz === abc); // prints "true" 

Однією з переконливих причин використання комбінованого стилю є атрибут "name" для об'єктів функцій (не підтримує Internet Explorer). В основному, коли ви визначаєте функцію типу

 function abc(){}; console.log(abc.name); // prints "abc" 

його ім'я автоматично призначається. Але коли ви визначаєте його як

 var abc = function(){}; console.log(abc.name); // prints "" 

його ім'я порожній - ми створили анонімну функцію і привласнили їй деяку змінну.

Ще одна вагома причина використовувати комбінований стиль - використовувати короткий внутрішнє ім'я, щоб посилатися на нього, надаючи довге неконфликтное ім'я для зовнішніх користувачів:

 // Assume really.long.external.scoped is {} really.long.external.scoped.name = function shortcut(n){ // Let it call itself recursively: shortcut(n - 1); // ... // Let it pass itself as a callback: someFunction(shortcut); // ... } 

У наведеному вище прикладі ми можемо зробити те ж саме з зовнішнім ім'ям, але воно буде занадто громіздким (і повільніше).

(Інший спосіб звернутися до самого себе - використовувати arguments.callee , який все ще відносно довгий і не підтримується в строгому режимі.)

Вниз, JavaScript обробляє обидва твердження по-різному. Це оголошення функції:

border=0
 function abc(){} 

abc тут визначається всюди в поточній області:

 // We can call it here abc(); // Works // Yet, it is defined down there. function abc(){} // We can call it again abc(); // Works 

Крім того, він піднявся за допомогою інструкції return :

 // We can call it here abc(); // Works return; function abc(){} 

Це вираз функції:

 var xyz = function(){}; 

xyz тут визначається з точки призначення:

 // We can't call it here xyz(); // UNDEFINED!!! // Now it is defined xyz = function(){} // We can call it here xyz(); // works 

Оголошення функції і вираз функції є реальною причиною того, що існує різниця, продемонстрована Грегом.

Кумедний факт:

 var xyz = function abc(){}; console.log(xyz.name); // Prints "abc" 

Особисто я віддаю перевагу декларацію "вираз функції", тому що таким чином я можу контролювати видимість. Коли я визначаю функцію типу

 var abc = function(){}; 

Я знаю, що я визначив функцію локально. Коли я визначаю функцію типу

 abc = function(){}; 

Я знаю, що я визначив його глобально, вказавши, що я не визначав abc в будь-якому місці ланцюжка областей. Цей стиль визначення стійкий навіть при використанні всередині eval() . хоча визначення

 function abc(){}; 

залежить від контексту і може залишити вас гадати, де він визначений, особливо в разі eval() - Відповідь: Це залежить від браузера.

1 846
03 дек. відповідь дан Eugene Lazutkin 03 дек. 2008-12-03 20:43 '08 о 20:43 2008-12-03 20:43

Ось короткий виклад стандартних форм, які створюють функції: (Спочатку написано для іншого питання, але адаптований після переходу в канонічне питання.)

терміни:

Швидкий список:

  • оголошення функції

  • "Анонімна" function Expression (яка, незважаючи на термін, іноді створює функції з іменами)

  • Іменована function Expression

  • Ініціалізатор функції доступу (ES5 +)

  • Вираз функції стрілки (ES2015 +) (яке, як і вираження анонімної функції, не містить явного імені і може створювати функції з іменами)

  • Оголошення методу в ініціалізатор об'єкта (ES2015 +)

  • Оголошення конструктора і методу в class (ES2015 +)

оголошення функції

Перша форма - це оголошення функції, яке виглядає так:

 function x() { console.log('x'); } 

Оголошення функції - це оголошення; це не твердження або вираз. Таким чином, ви не дотримуєтесь за ним з ; (Хоча це нешкідливо).

Оголошення функції обробляється, коли виконання входить в контекст, в якому воно з'являється, перед виконанням будь-якого покрокового коду. Створюваної їй функції присвоюється власне ім'я ( x в наведеному вище прикладі), і це ім'я поміщається в область, в якій з'являється оголошення.

Оскільки він обробляється перед будь-яким покроковим кодом в тому ж контексті, ви можете зробити щось на зразок цього:

 x(); // Works even though it above the declaration function x() { console.log('x'); } 

До ES2015 специфікація неохоплювала те, що повинен робити движок JavaScript, якщо ви помістили оголошення функції всередині структури управління, наприклад, try , if , switch , while і т.д., Наприклад:

 if (someCondition) { function foo() { // <===== HERE THERE } // <===== BE DRAGONS } 

І оскільки вони обробляються до запуску покрокового коду, складно знати, що робити, коли вони знаходяться в структурі управління.

Хоча це не було зазначено до ES2015, це було дозволене розширення для підтримки оголошень функцій в блоках. На жаль (і неминуче), різні двигуни робили різні речі.

Починаючи з ES2015, в специфікації сказано, що робити. Фактично, це дає три окремих дії:

  1. Якщо у вільному режимі немає в веб-браузері, движок JavaScript повинен робити одне
  2. Якщо у вільному режимі в веб-браузері, движок JavaScript повинен робити щось ще
  3. Якщо в строгому режимі (браузер чи ні), движок JavaScript повинен робити ще одну річ

Правила для вільних режимів хитрі, але в суворому режимі оголошення функцій в блоках прості: вони локальні для блоку (вони мають область видимості блоку, яка також є новою в ES2015), і вони піднімаються наверх блоку. так:

 "use strict"; if (someCondition) { foo(); // Works just fine function foo() { } } console.log(typeof foo); // "undefined" ('foo' is not in scope here // because it not in the same block) 

Вираз "анонімна" function

Друга поширена форма називається виразом анонімної функції:

 var y = function () { console.log('y'); }; 

Як і всі вирази, він обчислюється по досягненні покрокового виконання коду.

У ES5 створювана функція не має імені (вона анонімна). У ES2015, функції по можливості присвоюється ім'я, виводячи його з контексту. У наведеному вище прикладі ім'я буде y . Щось подібне відбувається, коли функція є значенням ініціалізатор властивості. (Для отримання докладної інформації про те, коли це відбувається, і про правила, знайдіть SetFunctionName в специфікації - він з'являється всюди.)

Іменована function Expression

Третя форма - це вираз з іменованої функцією ( "NFE"):

 var z = function w() { console.log('zw') }; 

Функція, яку вона створює, має власне ім'я (в даному випадку w ). Як і всі вирази, це оцінюється, коли воно досягається при покроковому виконанні коду. Ім'я функції не додається в область, в якій з'являється вираз; ім'я знаходиться в області дії самої функції:

 var z = function w() { console.log(typeof w); // "function" }; console.log(typeof w); // "undefined" 

Зверніть увагу, що NFE часто є джерелом помилок для реалізацій JavaScript. Наприклад, IE8 і більш ранні версії обробляють NFE абсолютно неправильно , створюючи дві різні функції в два різних моменту часу. Ранні версії Safari також мали проблеми. Доброю новиною є те, що в поточних версіях браузерів (IE9 і вище, поточний Safari) таких проблем більше немає. (Але, на жаль, на момент написання статті IE8 все ще широко використовується, і тому використання NFE з кодом для Інтернету в цілому все ще проблематично.)

Ініціалізатор функції доступу (ES5 +)

Іноді функції можуть проникнути в значній мірі непоміченими; що у випадку з функціями доступу. Ось приклад:

 var obj = { value: 0, get f() { return this.value; }, set f(v) { this.value = v; } }; console.log(obj.f); // 0 console.log(typeof obj.f); // "number" 

Зверніть увагу, що коли я використовував функцію, я не використовував () ! Це тому, що це функція доступу для властивості. Ми отримуємо і встановлюємо властивість звичайним способом, але за лаштунками викликається функція.

Ви також можете створювати функції доступу за допомогою Object.defineProperty , Object.defineProperties і менш відомого другого аргументу Object.create .

Вираз функції стрілки (ES2015 +)

ES2015 приносить нам функцію стрілки. Ось один приклад:

 var a = [1, 2, 3]; var b = a.map(n => n * 2); console.log(b.join(", ")); // 2, 4, 6 

Бачите, що n => n * 2 що ховається у виклику map() ? Це функція.

Кілька речей про функції стрілок:

  1. Вони не мають свій власний this . Замість цього вони закривають this контекст, в якому вони визначені. (Вони також близько над arguments і, де це доречно, super .) Це означає, що this в них так само, як this , де вони створені, і не може бути змінений.

  2. Як ви помітили вище, ви не використовуєте function ключового слова; замість цього ви використовуєте => .

Приклад n => n * 2 наведений вище, є однією з їх форм. Якщо у вас є кілька аргументів для передачі функції, ви використовуєте parens:

 var a = [1, 2, 3]; var b = a.map((n, i) => n * i); console.log(b.join(", ")); // 0, 2, 6 

(Пам'ятаєте, що Array#map передає запис в якості першого аргументу, а індекс - в якості другого.)

В обох випадках тіло функції є просто вираженням; яке значення функції буде автоматично результатом цього виразу (ви не використовуєте явний return ).

Якщо ви робите більше, ніж просто один вислів, використовуйте {} і явний return (якщо вам потрібно повернути значення), як зазвичай:

 var a = [ {first: "Joe", last: "Bloggs"}, {first: "Albert", last: "Bloggs"}, {first: "Mary", last: "Albright"} ]; a = a.sort((a, b) => { var rv = a.last.localeCompare(b.last); if (rv === 0) { rv = a.first.localeCompare(b.first); } return rv; }); console.log(JSON.stringify(a)); 

Версія без {... } називається функцією стрілки з тілом вираження або коротким тілом. (Також: Коротка функція стрілки.) Функція з {... } визначальним тіло, є функцією стрілки з тілом функції. (Також: функція багатослівній стрілки.)

Оголошення методу в ініціалізатор об'єкта (ES2015 +)

ES2015 допускає більш коротку форму оголошення властивості, яке посилається на функцію, звану визначенням методу; це виглядає так:

 var o = { foo() { } }; 

майже еквівалент в ES5 і більш ранніх версіях:

 var o = { foo: function foo() { } }; 

Різниця (крім багатослів'я) в тому, що метод може використовувати super , а функція - немає. Так, наприклад, якщо б у вас був об'єкт, який визначив (скажімо) valueOf з використанням синтаксису методу, він міг би використовувати super.valueOf() щоб отримати значення Object.prototype.valueOf яке повинно бути повернуто (перш ніж імовірно робити щось то ще з ним), тоді як Версія ES5 повинна була б замість Object.prototype.valueOf.call(this) зробити Object.prototype.valueOf.call(this) .

Це також означає, що метод має посилання на об'єкт, для якого він був визначений, тому, якщо цей об'єкт є тимчасовим (наприклад, ви передаєте його в Object.assign як один з вихідних об'єктів), синтаксис методу може означати, що об'єкт зберігається в пам'яті, коли в іншому випадку він міг би бути зібраний складальником сміття (якщо механізм JavaScript не може виявити цю ситуацію і не обробляє її, якщо жоден з методів не використовує super ).

Оголошення конструктора і методу в class (ES2015 +)

ES2015 надає нам синтаксис class , включаючи оголошені конструктори і методи:

 class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } getFullName() { return this.firstName + " " + this.lastName; } } 

Вище наведені два оголошення функцій: одне для конструктора, який отримує ім'я Person , і getFullName для getFullName , яке є функцією, призначеної для Person.prototype .

574
04 марта '14 в 16:35 2014-03-04 16:35 відповідь дан TJ Crowder 04 березня '14 о 16:35 2014-03-04 16:35

Говорячи про глобальному контексті, обидва оператори var і FunctionDeclaration в кінці створять властивість non-deleteable для глобального об'єкта, але значення обох може бути перезаписано.

Тонка різниця між двома способами полягає в тому, що коли процес Variable Instantiation запускається (до фактичного виконання коду), все ідентифікатори, оголошені за допомогою var буде инициализирован за допомогою undefined , а ті, які використовуються FunctionDeclaration , будуть доступні з цього моменту, наприклад:

  alert(typeof foo); // 'function', it already available alert(typeof bar); // 'undefined' function foo () {} var bar = function () {}; alert(typeof bar); // 'function' 

Призначення bar FunctionExpression виконується до виконання.

Глобальне властивість, створене FunctionDeclaration , може бути перезаписано без будь-яких проблем точно так же, як значення змінної, наприклад:

  function test () {} test = null; 

Ще одна очевидна різниця між вашими двома прикладами полягає в тому, що перша функція не має імені, але друга має її, що може бути дійсно корисно при налагодженні (наприклад, перевірка стека викликів).

Про вашому відредагованому першому прикладі ( foo = function() { alert('hello!'); }; ), Це незадекларовану завдання, я настійно рекомендую вам завжди використовувати ключове слово var .

При призначенні без оператора var , якщо контрольний ідентифікатор не знайдений в ланцюжку областей видимості, він стане видаляється властивістю глобального об'єкта.

Крім того, неоголошені завдання кидають ReferenceError на ECMAScript 5 в Strict Mode .

A повинен читати:

Примітка: ця відповідь була об'єднаний з іншого питання , в якому основним сумнівом і неправильним уявленням від OP було те, що ідентифікатори, оголошені за допомогою a FunctionDeclaration , не може бути перезаписано, що не так.

137
08 авг. відповідь дан CMS 08 Серпня. 2010-08-08 22:32 '10 о 22:32 2010-08-08 22:32

Два фрагмента коду, які ви розмістили там, будуть майже для всіх цілей поводитися однаково.

Однак різниця в поведінці полягає в тому, що з першим варіантом ( var functionOne = function() {} ) цю функцію можна викликати тільки після цієї точки в коді.

У другому варіанті ( function functionTwo() ) функція доступна для коду, який виконується вище, де оголошена функція.

Це пов'язано з тим, що в першому варіанті функція призначається змінної foo під час виконання. У другій функція призначається цьому ідентифікатору foo під час розбору.

Додаткова технічна інформація

JavaScript має три способи визначення функцій.

  • У вашому першому фрагменті показано вираз функції. Це пов'язано з використанням оператора "function" для створення функції - результат цього оператора може бути збережений в будь-якої змінної або об'єкті. Вираз функції є таким потужним. Вираз функції часто називають "анонімної функцією", оскільки воно не повинно мати імені,
  • Другий приклад - оголошення функції . Для створення функції використовується оператор "function". Функція надається під час розбору і може бути викликана в будь-якому місці цієї області. Ви можете зберегти його пізніше в змінної або об'єкті.
  • Третій спосіб визначення функції - конструктор "Function ()", який не показаний в вихідному повідомленні. Не рекомендується використовувати це, оскільки він працює так само, як eval() , у якого є свої проблеми.
115
20 апр. відповідь дан thomasrutter 20 Квітня. 2010-04-20 07:54 '10 в 7:54 2010-04-20 7:54

Найкраще пояснення Greg answer

 functionTwo(); function functionTwo() { } 

Чому немає помилок? Нам завжди вчили, що вирази виконуються від верху до низу (??)

Тому що:

Оголошення функцій і оголошення змінних завжди переміщаються ( hoisted ) невидимо в верхню частину своєї області за допомогою інтерпретатора JavaScript. Функціональні параметри і мовні імена, очевидно, вже є. ben cherry

Це означає, що такий код:

 functionOne(); --------------- var functionOne; | is actually | functionOne(); var functionOne = function(){ | interpreted |--> }; | like | functionOne = function(){ --------------- }; 

Зверніть увагу, що частина присвоювання декларацій не була піднята. Тільки ім'я піднято.

Але у випадку з оголошеннями функцій також буде піднято тіло всієї функції:

 functionTwo(); --------------- function functionTwo() { | is actually | }; function functionTwo() { | interpreted |--> } | like | functionTwo(); --------------- 
96
09 авг. відповідь дан simple_human 09 Серпня. 2014-08-09 05:45 '14 о 5:45 2014-08-09 5:45

Інші коментатори вже розглянули семантичну різницю двох перерахованих вище варіантів. Я хотів би відзначити стилістичну різницю: тільки варіація "призначення" може встановити властивість іншого об'єкта.

Я часто створюю модулі JavaScript з таким шаблоном:

 (function(){ var exports = {}; function privateUtil() { ... } exports.publicUtil = function() { ... }; return exports; })(); 

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

(Зверніть увагу також, що призначення повинно містити крапку з комою після інструкції, в той час як оголошення забороняє її.)

87
03 марта '11 в 22:19 2011-03-03 22:19 відповідь дан Sean McMillan 03 березня '11 о 22:19 2011-03-03 22:19

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

З

 if (condition){ function myfunction(){ // Some code } } 

це визначення myfunction перевизначити будь-яке попереднє визначення, так як воно буде виконано під час синтаксичного аналізу.

В той час як

 if (condition){ var myfunction = function (){ // Some code } } 

виконує правильну роботу визначення myfunction тільки тоді, коли виконується condition .

73
29 марта '13 в 16:26 2013-03-29 16:26 відповідь дан Mbengue Assane 29 березня '13 о 16:26 2013-03-29 16:26

Важливою причиною є додавання однієї і тільки однієї змінної в якості "кореня" вашого простору імен ...

 var MyNamespace = {} MyNamespace.foo= function() { } 

або

 var MyNamespace = { foo: function() { }, ... } 

Існує безліч методів для простору імен. Це стає більш важливим з безліччю доступних модулів JavaScript.

Також см. Як оголосити простір імен в JavaScript?

59
08 авг. відповідь дан Rob 08 Серпня. 2010-08-08 22:44 '10 о 22:44 2010-08-08 22:44

Hoisting - це дія інтерпретаторів JavaScript для переміщення всіх оголошень змінних і функцій в початок поточної обсяг.