Encontramos uma postagem interessante sobre Javascript IIFE no blog Desenvolvimento Para Web, que explica em muitos detalhes a sintaxe e as funcionalidades do IIFE. A equipe da Turbosite repassa esse conhecimento para os leitores do nosso blog, com a postagem a seguir.
JavaScript puro ou Vanilla JavaScript (felizmente) está voltando. E com força. Mas, quando você decide continuar nos estudos e começa a inspecionar códigos alheios, inevitavelmente você se depara com algo como:
(function (window, document, undefined) { //})(window, document);
E você encontra isso em diversos códigos; e vê outros desenvolvedores indicando; e colegas dizendo que esta é uma boa prática. Mas você sabe o que, realmente, isso significa? Continue lendo.
Frequentemente desenvolvedores descobrindo (ou redescobrindo) o JavaScript puro se perguntam sobre IIFE (Expressão de Função Imediatamente Invocada ou Immediately-Invoked Function Expression), frequentemente apresentado da seguinte maneira:
(function (window, document, undefined) { //})(window, document);
Na verdade, várias coisas estão acontecendo aí. Analisemos cada uma delas.
Escopo privado em IIFE
JavaScript tem maneiras interessantes de trabalhar com escopo de função; então, primeiramente essa expressão cria um “âmbito privado” (private scope). Por exemplo:
(function (window, document, undefined) { var name = 'Foo';})(window, document);console.log(name); // "name" não definido, está em um escopo diferente
Como funciona IIFE
Uma função normal se parece com isso:
var logMyName = function (name) { console.log(name);};logMyName('Foo');
Ela é chamada (invoke) quando necessário, por escolha do programador, em qualquer ponto do código em que se queira e/ou seja possível.
A razão pela qual essa IIFE em JavaScript foi criada, foi para ser uma expressão de função imediatamente invocada, o que significa que elas são imediatamente chamadas em tempo de execução – também, não é possível chamá-la novamente, já que elas só rodam uma vez, mais ou menos assim:
var logMyName = (function (name) { console.log(name); // Todd})('Todd');
O pulo-do-gato aqui é isso (que já foi atribuído a uma variável no exemplo anterior):
(function () {})();
O par de parênteses extra é necessário. Isso não funciona:
function () {}();
Embora existam vários truques que podem ser feitos para “enganar” o JavaScript e fazê-lo funcionar. Este, por exemplo, força o parser de JavaScript a tratar o código que segue !
como uma expressão:
!function () {}();
E existem variantes:
+function () {}(); -function () {}(); ~function () {}();
Mas não é preciso se forçar a decorar isso para usar a todo momento para fazer uma IIFE.
Argumentos da IIFE
Agora que foi explicado como isso funciona, passemos para os argumento da IIFE:
(function (window) {})(window);
Como isso funciona? Lembre-se, (window);
é onde a função é chamada, e onde é passado o Objeto window
. Então ele é passado para a função – que também foi chamada de window
.
Então, o que mais é possível fazer? Passar tudo o que for necessário! Por exemplo, passar o Objeto document
:
(function (window, document) { // é possível usar "window" e "document" normalmente})(window, document);
A resolução de variáveis locais é mais rápida que a de variáveis globais, mas esta está numa escala enorme e dificilmente alguém vai perceber o aumento da velocidade – mas vale a pena considerar se há muitas referências a globais!
E sobre o undefined
?
Em ECMAScript 3, undefined
é mutável. Isso significa que o valor poderia ser transferido com, por exemplo, undefined = true;
…! Felizmente, no ECMAScript 5 em modo estrito ('use strict';
) o parser lança um erro quando se depara com isso. Antes disso, começou-se a proteger uma IIFE da maneira já vista:
(function (window, document) { // é possível usar "window" e "document" normalmente})(window, document);
(function (window, document, undefined) {})(window, document);
O que significa que se alguém fizesse isso estaria tudo bem:
undefined = true;(function (window, document, undefined) { // "undefined" é uma variável local undefined})(window, document);
Minificando
Ao minificar as variáveis locais é que a grandiosidade do padrão IIFE realmente pode ser vista. Nomes de variáveis locais não são realmente necessários se estas foram passadas dessa maneira, então, é possível dar o nome que quiser para elas.
Por exemplo, mudar disso:
(function (window, document, undefined) { console.log(window); // Object window})(window, document);
Pra isso:
(function (a, b, c) { console.log(a); // Object window})(window, document);
Imagine todas as referências a bibliotecas e window
e document
minificadas. Claro que você não precisa parar por aí; é possível passar jQuery, também, ou qualquer outra coisa que esteja disponível dentro no escopo léxico:
(function ($, window, document, undefined) { // use $ para se referir à jQuery // $(document).addClass('test');})(jQuery, window, document); (function (a, b, c, d) { // Se torna // a(c).addClass('test');})(jQuery, window, document);
Isso também significa que você não precisa chamar jQuery.noConflict();
ou qualquer coisa como $
é atribuído localmente ao módulo. Aprender como escopos e variáveis globais/locais funcionam ajuda ainda mais.
Passar o código por um bom minificador certamente garantirá a renomeação de undefined
para c
(por exemplo, e apenas se for usado). É importante atentar para o fato de que o nome undefined
é irrelevante; só é preciso saber que o Objeto referenciado é indefinido (undefined) e, como undefined
não tem qualquer significado especial – undefined
é o valor que o JavaScript dá às coisas que são declaradas mas não têm nenhum valor (nem em IIFE).
Ambientes globais não-navegador
Devido a algumas tecnologias, Node.js, o navegador não é sempre o Objeto global, o que pode ser realmente problemático se você está tentando criar uma IIFE para funcionar em múltiplos ambientes. Por esta razão, há determinadas “alternativas”, como isso:
(function (root) {})(this);
Em um navegador, o ambiente global this
se refere ao Objeto window
, de modo que não é preciso passar em window
, é sempre possível reduzir para this
.
Alguns preferem o nome root
, já que ele pode se referir tanto a ambientes não-navegador, quanto à raiz do browser.
Se você estiver interessado em uma solução universal (muito útil para ser usada para a criação de módulos em projetos de código aberto) é o UMD (Universal Module Definition):
(function (root, factory) { if (typeof define === 'function' && define.amd) { define(factory); } else if (typeof exports === 'object') { module.exports = factory; } else { root.MYMODULE = factory(); }})(this, function () { // });
A função está sendo invocado com outra função sendo passada para ela. É possível, então, atribuí-la ao ambiente relevante “de dentro”. No navegador, root.MYMODULE = factory();
é o módulo IIFE; em qualquer outro ambiente (como Node.js), será usado module.exports
ou requireJS se define === 'function' && define.amd
resolver como true
.
Conclusão
Como foi visto, usar IIFE (Expressão de Função Imediatamente Invocada ou Immediately-Invoked Function Expression) é extramente benéfico. Além de ajudar na performance, por permitir que não seja preciso lookups no código a procura de variáveis globais, permite que seu código possa ficar “isolado”, protegendo o escopo de uma porção de código ou módulo do ambiente em que foi colocado.
O fato de diversos frameworks e bibliotecas JavaScript de renome como jQuery, Backbone.js, Modernizr e outros usarem IIFE já é bastante significativo. Use código de qualidade para fazer código de qualidade.