Объясните синтаксис инкапсулированной анонимной функции


373

Резюме

Можете ли вы объяснить причину синтаксиса инкапсулированных анонимных функций в 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
Также прочитайте о назначении этой конструкции или проверьте ( техническое ) объяснение (также здесь ). Для размещения скобок, смотрите этот вопрос об их местонахождении .
Берги

ОТ: Для тех, кто хочет знать, где эти анонимные функции часто используются, пожалуйста, прочитайте адекватно
good.com/JavaScript-Module-Pattern-In-Depth.html

Это типичный случай выражений немедленного вызова функций (IIFE).
Амину Кано

Ответы:


410

Он не работает, потому что он анализируется как a FunctionDeclaration, и идентификатор имени объявления функции является обязательным .

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

Грамматика FunctionDeclarationвыглядит так:

function Identifier ( FormalParameterListopt ) { FunctionBody }

И FunctionExpressionс:

function Identifieropt ( FormalParameterListopt ) { FunctionBody }

Как вы можете видеть, токен Identifier(Identifier opt ) FunctionExpressionявляется необязательным, поэтому мы можем иметь выражение функции без определенного имени:

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

Или именованное выражение функции:

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

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

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

function foo () {} // FunctionDeclaration

0,function foo () {} // FunctionExpression

Парсер знает, является ли он FunctionDeclarationили a FunctionExpression, в зависимости от контекста, в котором он появляется.

В приведенном выше примере второй является выражением, поскольку оператор запятой также может обрабатывать только выражения.

С другой стороны, FunctionDeclarations может фактически появляться только в так называемом Programкоде, что означает код снаружи в глобальной области видимости и внутри FunctionBodyдругих функций.

Следует избегать функций внутри блоков, поскольку они могут привести к непредсказуемому поведению, например:

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

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

Приведенный выше код на самом деле должен генерировать a SyntaxError, поскольку a Blockможет содержать только операторы (а спецификация ECMAScript не определяет какой-либо оператор функции), но большинство реализаций являются терпимыми и просто возьмут вторую функцию, которая предупреждает 'false!'.

Реализации Mozilla -Rhino, SpiderMonkey, имеют другое поведение. Их грамматика содержит нестандартный оператор Function, означающий, что функция будет оцениваться во время выполнения , а не во время анализа, как это происходит с FunctionDeclarations. В этих реализациях мы получим первую определенную функцию.


Функции могут быть объявлены по-разному, сравните следующее :

1- Функция, определенная конструктором Function, назначенным переменной multiply :

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

2- Объявление функции функции с именем multiply :

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

3- Функциональное выражение, присвоенное переменной multiply :

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

4- Именованное выражение функции func_name , присвоенное переменной multiply :

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

2
CMS ответ правильный. Для превосходного всестороннего объяснения объявлений функций и выражений, см. Эту статью kangax .
Тим Даун

Это отличный ответ. Кажется, это тесно связано с тем, как анализируется исходный текст, и структурой БНФ. в вашем примере 3 я должен сказать, что это выражение функции, потому что оно следует за знаком равенства, тогда как эта форма является объявлением / выражением функции, когда она появляется в строке сама по себе? Интересно, какова была бы цель этого - это просто интерпретировать как объявление именованной функции, но без имени? Какой цели это служит, если вы не присваиваете ее переменной, называете ее или вызываете?
Бретон

1
Ага. Очень полезный. Спасибо, CMS. Эта часть документации Mozilla, на которую вы
ссылаетесь,

1
+1, хотя у вас была закрывающая скобка в неправильной позиции в выражении функции :-)
NickFitz

1
@GovindRai, Нет. Объявления функций обрабатываются во время компиляции, а дублированное объявление функций отменяет предыдущее объявление. Во время выполнения объявление функции уже доступно, и в этом случае доступно то, которое предупреждает «ложь». Для получения дополнительной информации, читайте, что вы не знаете, JS
Saurabh Misra

50

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

Я хотел бы упомянуть, однако, одну очень важную вещь, которая заключается в том, что фрагмент кода Premasagar не будет работать, даже если он дал ему идентификатор имени.

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. поместите пустой оператор перед ним

  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 не нравятся фигурные скобки внутри открытых скобок.


Так почему же этот синтаксис работает, когда вы присваиваете выполненный результат переменной, а не автономно?
Пейсли

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') }()

!- negation 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.