Qual é o objetivo de agrupar arquivos Javascript inteiros em funções anônimas como “(function () {…}) ()”?

584

Ultimamente, tenho lido muito Javascript e tenho notado que o arquivo inteiro está agrupado da seguinte forma nos arquivos .js a serem importados.

(function() {
    ... 
    code
    ...
})();

Qual é a razão para fazer isso, em vez de um simples conjunto de funções construtoras?

Andrew Kou
fonte
6
Como imagino que isso será usado por muitas pessoas, não esqueça do fechamento;
dgh 31/03
5
Essa técnica é chamada "IIFE", eu acho. Isso significa expressão de função imediatamente chamada en.wikipedia.org/wiki/Immedately-invoked_function_expression #
Adrien Be

Respostas:

786

Geralmente é para namespace (veja mais adiante) e controle a visibilidade de funções-membro e / ou variáveis. Pense nisso como uma definição de objeto. O nome técnico para ele é uma expressão de função chamada imediatamente chamada (IIFE). Os plugins jQuery geralmente são escritos assim.

Em Javascript, você pode aninhar funções. Portanto, o seguinte é legal:

function outerFunction() {
   function innerFunction() {
      // code
   }
}

Agora você pode ligar outerFunction(), mas a visibilidade de innerFunction()é limitada ao escopo de outerFunction(), o que significa que é privada outerFunction(). Basicamente, segue o mesmo princípio que as variáveis ​​em Javascript:

var globalVariable;

function someFunction() {
   var localVariable;
}

Correspondentemente:

function globalFunction() {

   var localFunction1 = function() {
       //I'm anonymous! But localFunction1 is a reference to me!
   };

   function localFunction2() {
      //I'm named!
   }
}

No cenário acima, você pode ligar globalFunction()de qualquer lugar, mas não pode ligar localFunction1oulocalFunction2 .

O que você está fazendo ao escrever (function() { ... })()é transformar o código dentro do primeiro conjunto de parênteses em uma função literal (o que significa que todo o "objeto" é na verdade uma função). Depois disso, você invoca automaticamente a função (a final ()) que acabou de definir. Portanto, a principal vantagem disso, como mencionei antes, é que você pode ter métodos / funções e propriedades particulares:

(function() {
   var private_var;

   function private_function() {
     //code
   }
})();

No primeiro exemplo, você invocaria explicitamente globalFunctionpor nome para executá-lo. Ou seja, você faria apenas globalFunction()para executá-lo. Mas no exemplo acima, você não está apenas definindo uma função; você está definindo e invocando de uma só vez. Isso significa que, quando o arquivo JavaScript é carregado, ele é imediatamente executado. Claro, você poderia fazer:

function globalFunction() {
    // code
}
globalFunction();

O comportamento seria basicamente o mesmo, exceto por uma diferença significativa: você evita poluir o escopo global ao usar um IIFE (como conseqüência, isso também significa que você não pode invocar a função várias vezes, pois ela não tem nome, mas como essa função deve ser executada apenas quando realmente não for um problema).

O mais interessante do IIFEs é que você também pode definir as coisas internas e apenas expor as partes que deseja para o mundo exterior (um exemplo de espaço para nome para que você possa basicamente criar sua própria biblioteca / plugin):

var myPlugin = (function() {
 var private_var;

 function private_function() {
 }

 return {
    public_function1: function() {
    },
    public_function2: function() {
    }
 }
})()

Agora você pode ligar myPlugin.public_function1(), mas não pode acessar private_function()! Tão parecido com uma definição de classe. Para entender isso melhor, recomendo os seguintes links para algumas leituras adicionais:

EDITAR

Eu esqueci de mencionar. Nessa final (), você pode passar o que quiser dentro. Por exemplo, quando você cria plugins jQuery, você passa jQueryou $assim:

(function(jQ) { ... code ... })(jQuery) 

Então, o que você está fazendo aqui é definir uma função que aceite um parâmetro (chamado de jQvariável local e conhecida apenas por essa função). Então você invoca a função automaticamente e passa um parâmetro (também chamado jQuery, mas este é do mundo exterior e uma referência ao próprio jQuery). Não há necessidade urgente de fazer isso, mas existem algumas vantagens:

  • Você pode redefinir um parâmetro global e atribuir um nome que faça sentido no escopo local.
  • Há uma pequena vantagem de desempenho, pois é mais rápido pesquisar as coisas no escopo local, em vez de ter que subir a cadeia de escopo no escopo global.
  • Existem benefícios para compactação (minificação).

Descrevi anteriormente como essas funções são executadas automaticamente na inicialização, mas se elas são executadas automaticamente, quem está passando os argumentos? Essa técnica assume que todos os parâmetros necessários já estão definidos como variáveis ​​globais. Portanto, se o jQuery já não estivesse definido como uma variável global, este exemplo não funcionaria. Como você pode imaginar, o que o jquery.js faz durante a inicialização é definir uma variável global 'jQuery', bem como sua variável global '$' mais famosa, que permite que esse código funcione após a inclusão do jQuery.

Vivin Paliath
fonte
14
Muito legal, eu entendo bem o espaço para nome, mas já vi muito desse seu último exemplo e não consegui descobrir o que as pessoas estavam tentando alcançar. Isso realmente esclarece as coisas.
Andrew Kou
34
Postagem incrível. Muito obrigado.
Darren
4
Eu acho que adicionar um ponto e vírgula à esquerda e à esquerda ';' tornaria o exemplo completo - ;(function(jQ) { ... code ... })(jQuery);dessa maneira, se alguém deixasse um ponto e vírgula em seu script, não quebraria o seu, especialmente se você planeja minificar e concatenar seu script com outro.
Taras Alenin
3
bom post, eu gosto da ênfase em variáveis ​​privadas. Também gosto da abertura do módulo-padrão / closures (public_function1 & public_function2) e como você passa variáveis, mesmo que um pouco fora do escopo seja uma boa introdução. Eu também adicionei uma resposta, esta focando no que suponho que sejam as raízes da sintaxe e das diferenças entre declaração de função e expressão de função e o que eu acho que é "apenas uma convenção" vs "a única maneira de alcançar esse resultado".
Adrien Seja
4
Ótimo post, acho que talvez mais sobre como é benéfico passar variáveis ​​para a função de execução automática. O contexto na função de execução automática é limpo - sem dados. Você pode transmitir o contexto fazendo isso, o (function (context) { ..... })(this)que lhe permite anexar o que quiser ao contexto pai, expondo-o.
Callum Linington
79

Em resumo

Sumário

Em sua forma mais simples, essa técnica visa agrupar o código dentro de um escopo de função .

Ajuda a diminuir as chances de:

  • colidindo com outros aplicativos / bibliotecas
  • poluentes escopo superior (provavelmente global)

Ele não detectar quando o documento está pronto - não é algum tipo de document.onloadnemwindow.onload

É comumente conhecido como um Immediately Invoked Function Expression (IIFE)ouSelf Executing Anonymous Function .

Código explicado

var someFunction = function(){ console.log('wagwan!'); };

(function() {                   /* function scope starts here */
  console.log('start of IIFE');

  var myNumber = 4;             /* number variable declaration */
  var myFunction = function(){  /* function variable declaration */
    console.log('formidable!'); 
  };
  var myObject = {              /* object variable declaration */
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
})();                           /* function scope ends */

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

No exemplo acima, qualquer variável definida na função (ou seja, declarada usando var) será "privada" e acessível somente dentro do escopo da função (como Vivin Paliath coloca). Em outras palavras, essas variáveis ​​não são visíveis / alcançáveis ​​fora da função. Veja demonstração ao vivo .

Javascript tem escopo de função. "Parâmetros e variáveis ​​definidos em uma função não são visíveis fora da função e que uma variável definida em qualquer lugar dentro de uma função é visível em qualquer lugar dentro da função." (de "Javascript: as boas partes").


Mais detalhes

Código Alternativo

No final, o código publicado anteriormente também pode ser feito da seguinte maneira:

var someFunction = function(){ console.log('wagwan!'); };

var myMainFunction = function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
};

myMainFunction();          // I CALL "myMainFunction" FUNCTION HERE
someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

Veja demonstração ao vivo .


As raízes

Iteração 1

Um dia, alguém provavelmente pensou "deve haver uma maneira de evitar nomear 'myMainFunction', já que tudo o que queremos é executá-lo imediatamente".

Se você voltar ao básico, descobrirá que:

  • expression: algo avaliando um valor. ie3+11/x
  • statement: linha (s) de código fazendo algo, mas não avalia como um valor. ieif(){}

Da mesma forma, as expressões de função são avaliadas para um valor. E uma conseqüência (presumo?) É que eles podem ser imediatamente invocados:

 var italianSayinSomething = function(){ console.log('mamamia!'); }();

Portanto, nosso exemplo mais complexo se torna:

var someFunction = function(){ console.log('wagwan!'); };

var myMainFunction = function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
}();

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

Veja demonstração ao vivo .

Iteração 2

O próximo passo é o pensamento "por que ter var myMainFunction =se nem sequer o usamos !?".

A resposta é simples: tente remover isso, como abaixo:

 function(){ console.log('mamamia!'); }();

Veja demonstração ao vivo .

Não funcionará porque "declarações de função não são invocáveis" .

O truque é que, removendo var myMainFunction =, transformamos a expressão da função em uma declaração de função . Consulte os links em "Recursos" para obter mais detalhes sobre isso.

A próxima pergunta é "por que não posso mantê-lo como uma expressão de função com algo diferente var myMainFunction =?

A resposta é "você pode" e, na verdade, existem várias maneiras de fazer isso: adicionar a +, a !, a -ou talvez colocar parênteses (como agora é feito por convenção) e muito mais, acredito. Como exemplo:

 (function(){ console.log('mamamia!'); })(); // live demo: jsbin.com/zokuwodoco/1/edit?js,console.

ou

 +function(){ console.log('mamamia!'); }(); // live demo: jsbin.com/wuwipiyazi/1/edit?js,console

ou

 -function(){ console.log('mamamia!'); }(); // live demo: jsbin.com/wejupaheva/1/edit?js,console

Assim, depois que a modificação relevante é adicionada ao que era nosso "Código Alternativo", retornamos exatamente ao mesmo código que o usado no exemplo "Código Explicado"

var someFunction = function(){ console.log('wagwan!'); };

(function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
})();

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

Leia mais sobre Expressions vs Statements:


Escopos de desmistificação

Uma coisa que podemos nos perguntar é "o que acontece quando você NÃO define a variável 'adequadamente' dentro da função - ou seja, faz uma atribuição simples?"

(function() {
  var myNumber = 4;             /* number variable declaration */
  var myFunction = function(){  /* function variable declaration */
    console.log('formidable!'); 
  };
  var myObject = {              /* object variable declaration */
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  myOtherFunction = function(){  /* oops, an assignment instead of a declaration */
    console.log('haha. got ya!');
  };
})();
myOtherFunction();         // reachable, hence works: see in the console
window.myOtherFunction();  // works in the browser, myOtherFunction is then in the global scope
myFunction();              // unreachable, will throw an error, see in the console

Veja demonstração ao vivo .

Basicamente, se uma variável que não foi declarada em seu escopo atual tiver um valor atribuído, "uma pesquisa na cadeia de escopo ocorrerá até encontrar a variável ou atingir o escopo global (no momento em que será criada)".

Quando em um ambiente de navegador (versus um ambiente de servidor como nodejs), o escopo global é definido pelo windowobjeto. Por isso, podemos fazer window.myOtherFunction().

Minha dica "Boas práticas" sobre este tópico é sempre usar varao definir algo : seja um número, objeto ou função e até mesmo no escopo global. Isso torna o código muito mais simples.

Nota:

  • O javascript não possui block scope(Atualização: variáveis ​​locais do escopo do bloco adicionadas no ES6 .)
  • javascript possui apenas function scope& global scope( windowescopo em um ambiente de navegador)

Leia mais sobre Javascript Scopes:


Recursos


Próximos passos

Depois de obter esse IIFEconceito, ele leva ao module pattern, o que geralmente é feito ao alavancar esse padrão IIFE. Diverta-se :)

Adrien Be
fonte
Muito útil. Muito obrigado!
Christoffer Helgelin Hald
Bom, eu prefiro a demonstração versão :)
Fabrizio Bertoglio
Uma ótima explicação. Obrigado!
Vikram Khemlani 16/11/19
26

Javascript em um navegador realmente tem apenas alguns escopos efetivos: escopo de função e escopo global.

Se uma variável não está no escopo da função, está no escopo global. E variáveis ​​globais são geralmente ruins, portanto, isso é uma construção para manter as variáveis ​​de uma biblioteca para si.

Gareth
fonte
1
Mas a própria função construtora não fornece escopo para suas próprias variáveis?
Andrew Kou
1
Sim, cada função definida nesta biblioteca pode definir suas próprias variáveis locais, mas isso permite que as variáveis para ser compartilhada entre as funções sem eles vazamento de fora da biblioteca
Gareth
@Gareth, portanto, isso permite variáveis ​​"globais" dentro de um escopo (;
Francisco Presencia
2
@FranciscoPresencia "global dentro de um escopo" não é uma frase útil, porque é basicamente isso que significa "escopo". O ponto principal do escopo "global" é que é especificamente o escopo ao qual todos os outros escopos têm acesso.
Gareth
19

Isso é chamado de fechamento. Basicamente, sela o código dentro da função para que outras bibliotecas não interfiram nele. É semelhante à criação de um espaço para nome em idiomas compilados.

Exemplo. Suponha que eu escreva:

(function() {

    var x = 2;

    // do stuff with x

})();

Agora outras bibliotecas não podem acessar a variável que xeu criei para usar na minha biblioteca.

Joel
fonte
7
Cuidado com sua terminologia. O espaço para nome implica que as variáveis ​​podem ser acessadas de fora endereçando o espaço para nome (geralmente usando um prefixo). Embora isso seja possível em Javascript, isso não é o que é demonstrado aqui
Gareth
Concordo que não é exatamente como um espaço de nomes, no entanto, você pode fornecer uma funcionalidade semelhante, retornando um objeto com propriedades que você deseja publicar: (function(){ ... return { publicProp1: 'blah' }; })();. Obviamente, não é perfeitamente paralelo ao namespacing, mas pode ajudar a pensar dessa maneira.
Joel
no seu exemplo, x ainda é uma variável privada ... Apesar de envolvê-lo em um IIFE. vá em frente e tentar o acesso x fora da função, você não pode ..
RayLoveless
Seu ponto não é válido. Mesmo na função a seguir, outras bibliotecas não podem acessar x. function () {var x = 2}
RayLoveless 12/09
@RayLoveless Concordo. Eu não contradigo essa afirmação. De fato, fiz a mesma afirmação que a última frase desta resposta.
Joel
8

Você também pode usar fechamentos de função como dados em expressões maiores, como neste método para determinar o suporte ao navegador para alguns dos objetos html5.

   navigator.html5={
     canvas: (function(){
      var dc= document.createElement('canvas');
      if(!dc.getContext) return 0;
      var c= dc.getContext('2d');
      return typeof c.fillText== 'function'? 2: 1;
     })(),
     localStorage: (function(){
      return !!window.localStorage;
     })(),
     webworkers: (function(){
      return !!window.Worker;
     })(),
     offline: (function(){
      return !!window.applicationCache;
     })()
    }
Kennebec
fonte
O que faz o !! Faz?
1,21 gigawatts
!! converte um valor em sua representação booleana (verdadeiro / falso).
Liam
7

Além de manter as variáveis ​​locais, um uso muito útil é ao escrever uma biblioteca usando uma variável global, você pode atribuir um nome de variável mais curto a ser usado na biblioteca. É frequentemente usado na criação de plugins jQuery, pois o jQuery permite desativar a variável $ que aponta para o jQuery, usando jQuery.noConflict (). Caso esteja desabilitado, seu código ainda pode usar $ e não quebrar se você apenas fizer:

(function($) { ...code...})(jQuery);
Coronus
fonte
3
  1. Para evitar conflitos com outros métodos / bibliotecas na mesma janela,
  2. Evite o escopo global, torne-o local,
  3. Para acelerar a depuração (escopo local),
  4. O JavaScript tem apenas escopo de função, portanto, ajudará na compilação de códigos.
Vivek Mehta
fonte
1

Também devemos usar 'use strict' na função scope para garantir que o código seja executado no "modo estrito". Código de exemplo mostrado abaixo

(function() {
    'use strict';

    //Your code from here
})();
Neha Jain
fonte
Por que devemos usar rigoroso?
Nbro 02/08/19
Verifique este artigo: stackoverflow.com/questions/1335851/…
Neha Jain 9/16
Realmente não responde à pergunta!
Pritam Banerjee
Pritam, é um uso de boas práticas. Por favor, faça investigação adequada antes de votar contra qualquer resposta
Neha Jain
1
'use strict' salva programadores ruins de si mesmos. E como a maioria dos programadores é ruim, ajuda a impedi-los de fazer coisas que definitivamente não deveriam estar fazendo e terminando em uma confusão de código que afunda rapidamente.
30517 MattE