Объясните синтаксис инкапсулированной анонимной функции - PullRequest
357 голосов
/ 28 октября 2009

Резюме

Можете ли вы объяснить причину синтаксиса инкапсулированных анонимных функций в 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);
}();

Можете ли вы объяснить это мне?

Ответы [ 10 ]

393 голосов
/ 28 октября 2009

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

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

Грамматика FunctionDeclaration выглядит следующим образом:

function Identifier ( FormalParameterListopt ) { FunctionBody }

И FunctionExpression с:

function Identifieropt ( FormalParameterListopt ) { FunctionBody }

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

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

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

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

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

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

function foo () {} // FunctionDeclaration

0,function foo () {} // FunctionExpression

Анализатор знает, является ли он FunctionDeclaration или FunctionExpression, в зависимости от контекста , где он появляется.

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

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

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

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

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

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

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


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

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;
};
48 голосов
/ 26 августа 2012

Несмотря на то, что это старый вопрос и ответ, в нем обсуждается тема, которая до сих пор заставляет многих разработчиков зацикливаться. Я не могу сосчитать количество кандидатов в разработчиков 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
};

Конечно, использование идентификаторов имен в ваших определениях функций всегда полезно, когда речь идет об отладке кода, но это совсем другое ...: -)

14 голосов
/ 09 октября 2016

Хорошие ответы уже опубликованы. Но я хочу отметить, что объявления функций возвращают пустую запись завершения:

14.1.20 - Семантика времени выполнения: оценка

FunctionDeclaration : function BindingIdentifier ( FormalParameters ) { FunctionBody }

  1. Return 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(){})().

8 голосов
/ 11 августа 2017

В 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 голосов
/ 02 декабря 2011

У меня есть еще одно небольшое замечание. Ваш код будет работать с небольшим изменением:

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

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

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

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

0 голосов
/ 10 марта 2019

Вы также можете использовать его как:

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

или

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

! - операция отрицания преобразует определение fn в выражение fn, поэтому вы можете немедленно вызвать его с помощью (). То же, что с использованием 0,fn def или void fn def

0 голосов
/ 24 мая 2018

Эти лишние скобки создают дополнительные анонимные функции между глобальным пространством имен и анонимной функцией, которая содержит код. А в функциях Javascript, объявленных внутри других функций, есть доступ только к пространству имен родительской функции, которая их содержит. Поскольку существует дополнительный объект (анонимная функция) между глобальной областью действия и фактической областью видимости кода, не сохраняется.

0 голосов
/ 07 марта 2017

Они могут использоваться с параметрами-аргументами, такими как

var x = 3; 
var y = 4;

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

будет 7

0 голосов
/ 23 июня 2016
(function(){
     alert(2 + 2);
 })();

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

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

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

0 голосов
/ 20 октября 2015

Возможно, более короткий ответ будет:

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

- это литерал функции , который определяет (анонимную) функцию. Дополнительная пара (), которая интерпретируется как выражение, на верхнем уровне не ожидается, только литералы.

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

находится в выражении выражения , которое вызывает анонимную функцию.

...