Объясните синтаксис инкапсулированной анонимной функции
Резюме
Можете ли вы объяснить причину синтаксиса инкапсулированных анонимных функций в 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}
- Вернуть 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).
Чтобы сделать это функциональным выражением, вы должны:
заключите это используя ()
поместите пустой оператор перед ним
присвоить его переменной.
В противном случае он будет рассматриваться как определение функции, и тогда вы не сможете одновременно вызывать / вызывать его следующим образом:
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 ); })();
находится в выражении выражения, которое вызывает анонимную функцию.