Explicar a sintaxe da função anônima encapsulada

372

Sumário

Você pode explicar o raciocínio por trás da sintaxe para funções anônimas encapsuladas em JavaScript? Por que isso funciona: (function(){})();mas isso não function(){}();:?


O que eu sei

Em JavaScript, cria-se uma função nomeada como esta:

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

Você também pode criar uma função anônima e atribuí-la a uma variável:

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

Você pode encapsular um bloco de código criando uma função anônima, colocando-a entre colchetes e executando-a imediatamente:

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

Isso é útil ao criar scripts modularizados, para evitar sobrecarregar o escopo atual ou global, com variáveis ​​potencialmente conflitantes - como no caso de scripts Greasemonkey, plugins jQuery, etc.

Agora entendo por que isso funciona. Os colchetes incluem o conteúdo e expõem apenas o resultado (tenho certeza de que há uma maneira melhor de descrevê-lo), como com (2 + 2) === 4.


O que eu não entendo

Mas não entendo por que isso não funciona igualmente:

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

Você pode me explicar isso?

Premasagar
fonte
39
Eu acho que todas essas notações variadas e formas de definir / configurar / chamar funções é a parte mais confusa do trabalho inicial com javascript. As pessoas tendem a não falar sobre eles também. Não é um ponto enfatizado em guias ou blogs. Isso me surpreende porque é algo confuso para a maioria das pessoas, e as pessoas fluentes em js devem ter passado por isso também. É como essa realidade tabu vazia da qual nunca se fala.
ahnbizcad
11
Leia também sobre o objetivo desse construto ou verifique uma explicação ( técnica ) (também aqui ). Para a colocação dos parênteses, consulte esta pergunta sobre sua localização .
Bergi
OT: Para aqueles que querem saber onde essas funções anônimas são muito usados, por favor leia adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html
Alireza Fattahi
Este é um caso típico de Expressões de Função Invocadas Imediatamente (IIFE).
Aminu Kano

Respostas:

410

Não funciona porque está sendo analisado como ae FunctionDeclarationo identificador de nome das declarações de função é obrigatório .

Quando você o coloca entre parênteses, ele é avaliado como FunctionExpressionae as expressões de função podem ser nomeadas ou não.

A gramática de a FunctionDeclarationé assim:

function Identifier ( FormalParameterListopt ) { FunctionBody }

E FunctionExpressions:

function Identifieropt ( FormalParameterListopt ) { FunctionBody }

Como você pode ver, o token Identifier(Identifier opt ) FunctionExpressioné opcional, portanto, podemos ter uma expressão de função sem um nome definido:

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

Ou expressão da função nomeada :

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

Os parênteses (formalmente chamado de operador de agrupamento ) podem envolver apenas expressões e uma expressão de função é avaliada.

As duas produções gramaticais podem ser ambíguas e podem parecer exatamente iguais, por exemplo:

function foo () {} // FunctionDeclaration

0,function foo () {} // FunctionExpression

O analisador sabe se é um FunctionDeclarationou um FunctionExpression, dependendo do contexto em que aparece.

No exemplo acima, o segundo é uma expressão porque o operador Vírgula também pode manipular apenas expressões.

Por outro lado, FunctionDeclarations poderia aparecer apenas no Programcódigo " ", significando código fora do escopo global e dentro FunctionBodyde outras funções.

As funções dentro dos blocos devem ser evitadas, pois podem levar a um comportamento imprevisível, por exemplo:

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

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

O código acima deve realmente produzir a SyntaxError, uma vez que a Blockpode conter apenas instruções (e a especificação ECMAScript não define nenhuma instrução de função), mas a maioria das implementações é tolerante e simplesmente aceita a segunda função, a que alerta 'false!'.

As implementações do Mozilla - Rhino, SpiderMonkey, - têm um comportamento diferente. Sua gramática contém uma Declaração de Função não padrão , o que significa que a função será avaliada no tempo de execução , não no tempo de análise, como acontece com FunctionDeclarations. Nessas implementações, obteremos a primeira função definida.


As funções podem ser declaradas de diferentes maneiras; compare o seguinte :

1- Uma função definida com o construtor Function atribuído à variável multiplica :

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

2- Uma declaração de função de uma função denominada multiply :

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

3- Uma expressão de função atribuída à variável multiplica :

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

4- Uma expressão de função nomeada func_name , atribuída à variável multiply :

var multiply = function func_name(x, y) {
    return x * y;
};
CMS
fonte
2
A resposta do CMS está correta. Para uma excelente explicação detalhada de declarações e expressões de funções, consulte este artigo por kangax .
Tim Down
Esta é uma ótima resposta. Parece estar intimamente ligado ao modo como o texto original é analisado - e à estrutura do BNF. no seu exemplo 3, devo dizer que é uma expressão de função porque segue um sinal de igual, onde esse formulário é uma declaração / declaração de função quando aparece sozinho em uma linha? Eu me pergunto qual seria o objetivo disso - é apenas interpretado como uma declaração de função nomeada, mas sem nome? Que finalidade isso serve se você não estiver atribuindo a uma variável, nomeando ou chamando?
Breton
11
Aha. Muito útil. Obrigado, CMS. Esta parte dos documentos do Mozilla aos quais você se vinculou é especialmente esclarecedora: developer.mozilla.org/En/Core_JavaScript_1.5_Reference/…
Premasagar 27/10/2009
11
+1, embora que você teve o suporte de fechamento na posição errada na expressão da função :-)
NickFitz
11
@GovindRai, No. As declarações de função são tratadas em tempo de compilação e uma declaração de função duplicada substitui a declaração anterior. No tempo de execução, a declaração da função já está disponível e, nesse caso, a que está disponível é a que alerta "false". Para mais informações, leia você não sabe JS
Saurabh Misra
50

Embora essa seja uma pergunta e resposta antigas, ele discute um tópico que até hoje gera muitos desenvolvedores em um loop. Não posso contar o número de candidatos a desenvolvedor JavaScript que entrevistei que não conseguiam me dizer a diferença entre uma declaração de função e uma expressão de função e que não tinham idéia do que é uma expressão de função imediatamente invocada.

Gostaria de mencionar, no entanto, uma coisa muito importante que é que o trecho de código de Premasagar não funcionaria, mesmo que ele tivesse dado um identificador de nome.

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

A razão pela qual isso não funcionou é que o mecanismo JavaScript interpreta isso como uma declaração de função seguida por um operador de agrupamento completamente não relacionado que não contém expressão, e os operadores de agrupamento devem conter uma expressão. De acordo com o JavaScript, o trecho de código acima é equivalente ao seguinte.

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

();

Outra coisa que gostaria de salientar que pode ser útil para algumas pessoas é que qualquer identificador de nome que você forneça para uma expressão de função é praticamente inútil no contexto do código, exceto dentro da própria definição de função.

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

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

Obviamente, o uso de identificadores de nome com suas definições de função é sempre útil quando se trata de código de depuração, mas isso é algo completamente diferente ... :-)

natlee75
fonte
17

Ótimas respostas já foram postadas. Mas quero observar que as declarações de função retornam um registro de conclusão vazio:

14.1.20 - Semântica de tempo de execução: avaliação

FunctionDeclaration : function BindingIdentifier ( FormalParameters ) { FunctionBody }

  1. Retorno NormalCompletion (vazio).

Esse fato não é fácil de observar, porque a maioria das maneiras de tentar obter o valor retornado converterá a declaração da função em uma expressão de função. No entanto, evalmostra:

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

Chamar um registro de conclusão vazio não faz sentido. É por isso function f(){}()que não pode funcionar. De fato, o mecanismo JS nem tenta chamá-lo, os parênteses são considerados parte de outra instrução.

Mas se você envolver a função entre parênteses, ela se tornará uma expressão de função:

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

As expressões de função retornam um objeto de função. E, portanto, você pode chamá-lo: (function f(){})().

Oriol
fonte
Pena que esta resposta seja esquecida. Embora não seja tão abrangente como a resposta aceito, ele fornece alguma informação extra muito útil e merece mais votos
Avrohom Yisrael
10

Em javascript, isso é chamado de Expressão de Função Imediatamente Invocada (IIFE) .

Para torná-lo uma expressão de função, você deve:

  1. coloque-o usando ()

  2. coloque um operador vazio antes de

  3. atribua-o a uma variável.

Caso contrário, será tratado como definição de função e você não poderá chamá-lo / invocá-lo ao mesmo tempo da seguinte maneira:

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

O texto acima dará a você erro. Porque você pode invocar apenas uma expressão de função imediatamente.

Isso pode ser alcançado de duas maneiras: Maneira 1:

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

Caminho 2:

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

Caminho 3:

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

caminho 4:

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

Tudo acima invocará imediatamente a expressão da função.

asmmahmud
fonte
3

Eu tenho apenas outro pequeno comentário. Seu código funcionará com uma pequena alteração:

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

Eu uso a sintaxe acima em vez da versão mais amplamente difundida:

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

porque não consegui fazer o recuo funcionar corretamente para arquivos javascript no vim. Parece que o vim não gosta das chaves nos parênteses abertos.

Andrei Bozantan
fonte
Então, por que essa sintaxe funciona quando você atribui o resultado executado a uma variável, mas não autônoma?
paislee 16/07/2012
11
@paislee - Como o mecanismo JavaScript interpreta qualquer instrução JavaScript válida que comece com a functionpalavra - chave como uma declaração de função. Nesse caso, o final ()é interpretado como um operador de agrupamento , que, de acordo com regras de JavaScript sintaxe, só pode e deve conter uma expressão JavaScript.
precisa saber é
11
@bosonix - Sua sintaxe preferida funciona bem, mas é uma boa ideia usar a "versão mais amplamente difundida" que você referenciou ou a variante em que () incluída no operador de agrupamento (aquele que Douglas Crockford recomenda fortemente) para obter consistência: é É comum usar IIFEs sem atribuí-los a uma variável, e é fácil esquecer de incluir esses parênteses se você não os usar de forma consistente.
precisa saber é
0

Talvez a resposta mais curta seja essa

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

é uma literal de função que define uma função (anônima). Um par () adicional, que é interpretado como uma expressão, não é esperado no nível superior, apenas literais.

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

está em um instrução de expressão que chama uma função anônima.

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

Acima é uma sintaxe válida, porque qualquer coisa passada entre parênteses é considerada como expressão de função.

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

Acima não é uma sintaxe válida. Como o analisador de sintaxe do script java procura o nome da função após a palavra-chave da função, pois não encontra nada, gera um erro.

Vithy
fonte
2
Embora sua resposta não esteja incorreta, a resposta aceita já cobre tudo isso no departamento.
Até Arnold
0

Você também pode usá-lo como:

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

ou

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

!- negation op converte a definição fn em expressão fn; portanto, você pode invocá-la imediatamente com (). O mesmo que usar 0,fn defouvoid fn def

Csaba K.
fonte
-1

Eles podem ser usados ​​com parâmetros-argumentos como

var x = 3; 
var y = 4;

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

resultaria em 7

Jude
fonte
-1

Esses parênteses extras criam funções anônimas extras entre o espaço para nome global e a função anônima que contém o código. E, em funções Javascript declaradas dentro de outras funções, somente é possível acessar o namespace da função pai que as contém. Como existe um objeto extra (função anônima) entre o escopo global e o escopo do código real, não é mantido.

Jarkko Hietala
fonte