Поясніть синтаксис інкапсульованої анонімної функції


372

Підсумок

Чи можете ви пояснити міркування синтаксису інкапсульованих анонімних функцій у JavaScript? Чому це працює: (function(){})();але це не так:function(){}(); :?


Що я знаю

У JavaScript створюється названа така функція:

function twoPlusTwo(){
    alert(2 + 2);
}
twoPlusTwo();

Ви також можете створити анонімну функцію та призначити її змінній:

var twoPlusTwo = function(){
    alert(2 + 2);
};
twoPlusTwo();

Ви можете інкапсулювати блок коду, створивши анонімну функцію, потім загорнувши її в дужки та виконавши її негайно:

(function(){
    alert(2 + 2);
})();

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

Тепер я розумію, чому це працює. В дужках додається вміст і викладається лише результат (я впевнений, що є кращий спосіб описати це), наприклад, з (2 + 2) === 4.


Що я не розумію

Але я не розумію, чому це також не працює однаково:

function(){
    alert(2 + 2);
}();

Чи можете ви мені це пояснити?


39
Я думаю, що всі ці різноманітні позначення та способи визначення / настройки / виклику функцій є найбільш заплутаною частиною спочатку роботи з javascript. Люди, як правило, також не говорять про них. Це не наголошений момент у путівниках чи блогах. Це дує мені на розум, тому що це щось заплутане для більшості людей, і люди, які вільно знають js, напевно, теж пройшли через це. Це як ця порожня табу-реальність, про яку ніколи не говорять.
ahnbizcad

1
Читайте також про мету цієї конструкції або перевірте ( технічне ) пояснення (також тут ). Про розміщення круглих дужок див. Це питання про їх розташування .
Бергі

OT: Для тих , хто хоче знати , де ці анонімні функції використовуються багато, будь ласка , прочитайте adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html
Реза Фаттаха

Це типовий вираз виразів негайно викликаних функцій (IIFE).
Аміну Кано

Відповіді:


410

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

Коли ви оточуєте його круглими дужками, він оцінюється як a FunctionExpression, а вирази функцій можуть бути названі чи ні.

Граматика FunctionDeclarationвиглядає приблизно так:

function Identifier ( FormalParameterListopt ) { FunctionBody }

І FunctionExpressions:

function Identifieropt ( FormalParameterListopt ) { FunctionBody }

Як ви бачите, що маркер Identifier(ідентифікатор opt ) не FunctionExpressionє обов'язковим, тому ми можемо мати вираз функції без визначеного імені:

(function () {
    alert(2 + 2);
}());

Або названий вираз функції:

(function foo() {
    alert(2 + 2);
}());

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

Дві граматичні постановки можуть бути неоднозначними, і вони можуть виглядати абсолютно однаково, наприклад:

function foo () {} // FunctionDeclaration

0,function foo () {} // FunctionExpression

Аналізатор знає, чи це a, FunctionDeclarationабо a FunctionExpression, залежно від контексту, де він з'являється.

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

З іншого боку, FunctionDeclarations насправді може з'являтися лише у тому, що називається Programкодом, тобто кодом, який знаходиться зовні в глобальному масштабі та всередині FunctionBodyінших функцій.

Функцій всередині блоків слід уникати, оскільки вони можуть вести непередбачувану поведінку, наприклад:

if (true) {
  function foo() {
    alert('true');
  }
} else {
  function foo() {
    alert('false!');
  }
}

foo(); // true? false? why?

Наведений вище код насправді повинен створювати SyntaxError , оскільки Blockможе містити лише заяви (а специфікація ECMAScript не визначає жодного оператора функції), але більшість реалізацій є терпимими і просто приймуть другу функцію, ту, що попереджає 'false!'.

Реалізації Mozilla -Rhino, SpiderMonkey, - відрізняються поведінкою. Їх граматика містить нестандартний виклад функції, що означає, що функція буде оцінена в час виконання , а не під час розбору, як це відбувається з FunctionDeclarations. У цих реалізаціях ми визначимо першу функцію.


Функції можна оголосити різними способами, порівняйте наступне :

1- Функція, визначена конструктором функцій, присвоєним змінній множити :

var multiply = new Function("x", "y", "return x * y;");

2- Оголошення функції функції, названої помножити :

function multiply(x, y) {
    return x * y;
}

3- Вираз функції, присвоєне змінній множитися :

var multiply = function (x, y) {
    return x * y;
};

4- Іменне вираження функції func_name , присвоєне змінній помножити :

var multiply = function func_name(x, y) {
    return x * y;
};

2
Відповідь CMS правильна. Для відмінного поглибленого пояснення декларацій функцій та виразів дивіться цю статтю на кенгу .
Тім Даун

Це чудова відповідь. Здається, це тісно пов'язане з тим, як розбирається вихідний текст - та структурою BNF. чи слід у вашому прикладі 3 сказати, що це вираження функції, оскільки воно слідує знаку рівності, коли ця форма є оголошенням / заявою функції, коли воно з'являється на рядку саме собою? Цікаво, яка мета цього полягатиме в тому, - чи це просто інтерпретується як названа функція оголошення, але без імені? Якої цілі це служить, якщо ви не присвоюєте його змінній, не називаєте її або називаєте?
Бретон

1
Ага. Дуже корисний. Спасибі, CMS Ця частина документів Mozilla, з якими ви пов’язані, особливо просвічує: developer.mozilla.org/En/Core_JavaScript_1.5_Reference/…
Premasagar

1
+1, хоча у вас виникла дужка в неправильному положенні у вислові функції :-)
NickFitz

1
@GovindRai, Ні. Декларації функції обробляються під час компіляції, а дублююча декларація функції перевищує попередню декларацію. Під час виконання декларація функції вже доступна, і в цьому випадку наявна є та, яка попереджає "помилкову". Для отримання додаткової інформації читайте, що ви не знаєте JS
Saurabh Misra

50

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

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

function someName() {
    alert(2 + 2);
}();

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

function someName() {
    alert(2 + 2);
}

();

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

var a = function b() {
    // do something
};
a(); // works
b(); // doesn't work

var c = function d() {
    window.setTimeout(d, 1000); // works
};

Звичайно, використання ідентифікаторів імен з визначеннями ваших функцій завжди корисно, якщо мова йде про налагодження коду, але це зовсім інше ... :-)


17

Чудові відповіді вже розміщені. Але хочу зазначити, що декларації функцій повертають порожній запис про завершення:

14.1.20 - Семантика виконання: оцінка

FunctionDeclaration : function BindingIdentifier ( FormalParameters ) { FunctionBody }

  1. Повернути NormalCompletion (порожній).

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

var r = eval("function f(){}");
console.log(r); // undefined

Викликати порожній запис про завершення не має сенсу. Ось чому function f(){}()не можна працювати. Насправді двигун JS навіть не намагається його викликати, дужки вважаються частиною іншого твердження.

Але якщо ви загортаєте функцію в дужки, вона стає виразом функції:

var r = eval("(function f(){})");
console.log(r); // function f(){}

Вирази функцій повертають об'єкт функції. І тому ви можете назвати це: (function f(){})().


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

10

У javascript це називається виразом функцій негайно викликаних функцій (IIFE) .

Для того, щоб зробити це виразом функції, вам потрібно:

  1. додайте його за допомогою ()

  2. розмістіть перед ним оператора "void"

  3. призначити його змінній.

Інакше це буде розглядатися як визначення функції, і тоді ви не зможете зателефонувати / викликати його одночасно таким чином:

 function (arg1) { console.log(arg1) }(); 

Сказане дасть вам помилку. Тому що ви можете лише викликати вираз функції негайно.

Цього можна досягти двома способами: Спосіб 1:

(function(arg1, arg2){
//some code
})(var1, var2);

Спосіб 2:

(function(arg1, arg2){
//some code
}(var1, var2));

Спосіб 3:

void function(arg1, arg2){
//some code
}(var1, var2);

спосіб 4:

  var ll = function (arg1, arg2) {
      console.log(arg1, arg2);
  }(var1, var2);

Все вище буде негайно викликати вираз функції.


3

Маю ще одне невелике зауваження. Ваш код буде працювати з невеликою зміною:

var x = function(){
    alert(2 + 2);
}();

Я використовую вищевказаний синтаксис замість більш широко розповсюдженої версії:

var module = (function(){
    alert(2 + 2);
})();

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


То чому цей синтаксис працює, коли ви присвоюєте виконаний результат змінній, але не окремо?
paislee

1
@paislee - Оскільки тлумачить двигун JavaScript будь-якого допустимий оператор JavaScript , починаючи з functionключовим словом як оголошення функції в цьому випадку трейлинг ()інтерпретуються як угруповання оператор , який, в відповідно до правил синтаксису в JavaScript, може тільки та повинен містити вираз JavaScript.
natlee75

1
@bosonix - Ваш уподобаний синтаксис працює добре, але це гарна ідея використовувати або "більш розповсюджену версію", на яку ви посилаєтеся, або варіант, де ()вкладено в межах оператора групування (той, який Дуглас Крокфорд настійно рекомендує) для послідовності: це звичайно використовувати IIFE, не призначаючи їх змінній, і легко забути включити ці обгорткові дужки, якщо ви не використовуєте їх послідовно.
natlee75

0

Можливо, коротша відповідь буде такою

function() { alert( 2 + 2 ); }

це функціональний буквал, який визначає (анонімну) функцію. Додатковий ()-пара, який інтерпретується як вираз, не очікується на рівні рівнів, лише літерали.

(function() { alert( 2 + 2 ); })();

є у виразі виразів, який викликає анонімну функцію.


0
(function(){
     alert(2 + 2);
 })();

Вище є правильним синтаксисом, оскільки все, що передається всередині дужок, розглядається як вираження функції.

function(){
    alert(2 + 2);
}();

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


2
Хоча ваша відповідь не є хибною, прийнята відповідь вже охоплює все це у відділі.
До Арнольда

0

Ви також можете використовувати його як:

! function() { console.log('yeah') }()

або

!! function() { console.log('yeah') }()

!- заперечення op перетворює визначення fn у fn вираз, тому ви можете негайно викликати його (). Те саме, що використовувати 0,fn defабоvoid fn def


-1

Їх можна використовувати з параметрами-аргументами типу

var x = 3; 
var y = 4;

(function(a,b){alert(a + b)})(x,y)

приведе до 7


-1

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.