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

Резюме

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

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

9 ответов

Решение

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

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

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

function Identifier ( FormalParameterListopt ) { FunctionBody }

А также FunctionExpression s:

function Identifieropt ( FormalParameterListopt ) { FunctionBody }

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

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

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

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

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

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

function foo () {} // FunctionDeclaration

0,function foo () {} // FunctionExpression

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

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

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

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

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

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

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

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


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

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;
};

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

FunctionDeclaration: function BindingIdentifier ( ФормальныеПараметры ){ 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(){})(),

В 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);

Все вышеизложенное немедленно вызовет выражение функции.

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

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

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

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

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

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

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

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

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

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

Их можно использовать с параметрами-аргументами, такими как

var x = 3; 
var y = 4;

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

будет результатом как 7

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

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

или же

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

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

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

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

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

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

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

Другие вопросы по тегам