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() - Адказ: Гэта залежыць ад браўзэра.

1846
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 было тое, што ідэнтыфікатары, абвешчаныя з дапамогай а 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 у 07:54 2010-04-20 07: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 у 05:45 2014/08/09 05: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 для перамяшчэння ўсіх аб'яваў зменных і функцый у пачатак бягучай аб'ём.