JavaScript e Padrão de Módulo

09:51

Continuando a postagem anterior JavaScript IIFE como contêiner de códigos, compartilhamos aqui mais um artigo do blog Desenvolvimento Para Web. A equipe da Turbosite repassa seu conhecimento sobre JavaScript Module Pattern (Padrão de Módulo), um padrão de desenvolvimento bastante comum para Javascript. As próximas linhas abaixo abrangem deste o estudo básico ao avançado desse padrão.

 

Anonymous Closures (Closures Anônimos)

Anonymous Closures são o construto fundamental em Module Pattern que tornam tudo isso possível e são uma das melhores característica de JavaScript: é possível criar uma função anônima e executá-la imediatamente. Todo o código que é executado dentro da função esta no escopo do closure (algo como “fechamento”), o que proporciona privacidade e estado durante toda a vida da aplicação.

(function () { // Todas variáveis e funções estão somente neste escopo // e ainda mantêm acesso a todos os globais}());

Observe a () em torno da função anônima. Isto é requerido pela linguagem, uma vez que declarações que começam com a função de token são sempre considerados declarações de função. Incluir () cria uma expressão de função.

 

Global Import

JavaScript tem um recurso conhecido como implied globals (globals implícitas). Sempre que um nome é usado, o interpretador volta pela cadeia de escopo em busca de uma declaração de variável (var) para esse nome. Se nada for encontrado, a variável é assumida como global; se é usado em um assignmente, o global é criado se não existir. Isso significa que o uso ou a criação de variáveis ​​globais em um closure anônimo é fácil. Infelizmente, isso leva a código difícil de gerir, já que não é óbvio (para humanos) que as variáveis ​​são globais em um determinado arquivo.

Felizmente, o recurso de função anônima fornece uma alternativa fácil a isso. Ao passar globais como parâmetros para a função anônima, que os importa para o código. Eis um exemplo:

(function ($, YAHOO) { // Agora se tem acesso às globais jQuery (como "$") e YAHOO nesse código}(jQuery, YAHOO));

 

Module Export

Às vezes, você não quer apenas usar globais, mas quer declará-los. É possível fazer isso facilmente ao exportá-los, usando o valor de retorno da função anônima. Se o fizer, completará o Padrão de Módulo básico. Veja um exemplo completo:

var MODULE = (function () { var my = {}, privateVariable = 1;  function privateMethod() { // ... }  my.moduleProperty = 1; my.moduleMethod = function () { // ... };  return my;}());

Observe que foi declarado um módulo global chamado MODULE com duas propriedades públicas: um método chamado MODULE.moduleMethod e uma variável chamada MODULE.moduleProperty. Além disso, ele mantém o estado interno privada usando o closure da função anônima — e também é possível importar globais necessários usando o padrão mostrado anteriormente.

 

Augmentation

Uma limitação do Module Pattern até agora é que o módulo inteiro deve estar em um arquivo. Quem já trabalhou em uma grande base de código entende o valor de divisão de código em vários arquivos. Felizmente, existe uma boa solução: Augmentation (Ampliação). Em primeiro lugar, é preciso importar o módulo; então, são adicionadas novas propriedades e; por fim, ele é exportado.

Aqui está um exemplo de uso de Augmentation:

var MODULE = (function (my) { my.anotherMethod = function () { // Método adicionado... }; return my;}(MODULE));

É usada a palavra-chave var novamente para consistência, apesar de não ser estritamente necessário. Após este código ser executado, o módulo terá ganho um novo método público chamado MODULE.anotherMethod. Este arquivo “ampliado” também manterá seu próprio estado interno privado e importações.

 

Loose Augmentation

Enquanto o exemplo acima requer a criação inicial do módulo em primeiro lugar e a ampliação acontecer depois disso, isso nem sempre é necessário. Uma das melhores coisas que um aplicativo JavaScript pode fazer em relação a desempenho é carregar scripts de forma assíncrona. É possível criar módulos flexíveis de várias partes que podem se carregar em qualquer ordem com o Loose Augmentation (Ampliação Fraca). Cada arquivo deve ter a seguinte estrutura:

var MODULE = (function (my) { // Adiciona recursos... return my;}(MODULE || {}));

Neste padrão, a instrução var é sempre necessária. Note que a importação vai criar o módulo se ele ainda não existir. Isto significa que você pode usar uma ferramenta como LABjs e carregar todos os seus arquivos de módulo em paralelo, sem a necessidade de bloquear.

 

Tight Augmentation

Tight Augmentation (Ampliação Forte) é excelente, mas coloca algumas limitações no módulo, dentre as quais se destaca a impossibilidade de poder substituir as propriedades do módulo de maneira segura. Também não é possível usar as propriedades do módulo de outros arquivos durante a inicialização — embora seja possível em tempo de execução após a inicialização. Loose Augmentation implica numa ordem estabelecida de carregamento, mas permite substituições.

Eis um exemplo simples (ampliando o módulo-exemplo original):

var MODULE = (function (my) { var old_moduleMethod = my.moduleMethod;  my.moduleMethod = function () { // sobrescrita de método com acesso ao antigo através de old_moduleMethod foi };  return my;}(MODULE));

MODULE.moduleMethod foi substituído, mas manteve uma referência para o método original, caso fosse necessário.

 

Cloning and Inheritance (Clonagem e Herança)

Dentre os Padrões de Module Pattern, talvez este seja a opção mais flexível. Ele permite que algumas composições elegantes, mas que vem à custa da flexibilidade: propriedades que são objetos ou funções não serão duplicadas e existirão como um objeto com duas referências. Mudando uma, mudará a outra. Isso pode ser corrigido para objetos com um processo de clonagem recursiva, mas, provavelmente, não pode ser fixado para funções — exceto, talvez, com eval().

var MODULE_TWO = (function (old) { var my = {}, key;  for (key in old) { if (old.hasOwnProperty(key)) { my[key] = old[key]; } }  var super_moduleMethod = old.moduleMethod; my.moduleMethod = function () { // sobrescreve método ao clonar, acesso ao super através de super_moduleMethod };  return my;}(MODULE));

 

Cross-File Private State (Estado Privado Entre-Arquivos)

Uma limitação grave de dividir um módulo em vários arquivos é que cada arquivo mantém seu próprio estado privado e não tem acesso ao estado privado dos outros arquivos. Mas isso pode ser corrigido. Aqui está um exemplo de um módulo com Ampliação Fraca que vai manter o estado privado em todos as ampliações:

var MODULE = (function (my) { var _private = my._private = my._private || {}, _seal = my._seal = my._seal || function () { delete my._private; delete my._seal; delete my._unseal; }, _unseal = my._unseal = my._unseal || function () { my._private = _private; my._seal = _seal; my._unseal = _unseal; };  // Acesso permanente a _private, _seal e _unseal  return my;}(MODULE || {}));

Qualquer arquivo pode definir propriedades em sua variável local _private e esta será imediatamente disponível para os outros. Uma vez que este módulo foi completamente carregado, o aplicativo deve chamar MODULE._seal(), que irá impedir o acesso externo ao interno _private.

Se este módulo fosse ampliado novamente, mais à frente na vida aplicativo um dos métodos internos, em qualquer arquivo, pode chamar _unseal() antes de carregar o novo arquivo e _seal() novamente depois de ter sido executado.

 

Submódulo

O padrão de Module Pattern Submódulo (Sub-module) é considerado pela maioria como o mais simples. Há muitos bons casos para a criação de submódulos. É como criar módulos regulares:

MODULE.sub = (function () { var my = {}; // ... return my;}());

Embora isso possa parecer bastante óbvio, vale a pena ser mencionado em um artigo desse tipo, haja vista que submódulos têm todas as capacidades avançadas de módulos normais, incluindo ampliação e estado privado.

 

Conclusão

A maioria dos Padrões Avançados de Module Pattern podem ser combinados uns com os outros para criar padrões mais úteis. Não raramente é possível encontrar, em aplicações mais complexas, a combinação de Ampliação Fraca, Estado Privado e Submódulos.

No decorrer do artigo, não se mencionou a respeito de performance, mas uma nota rápida sobre isso seria: Module Pattern é bom para performance! Ele passa por minificação muito bem, o que faz com que o download seja mais rápido. Usar Loose Augmentation permite downloads paralelos, fáceis e sem bloqueios. O tempo de inicialização é, provavelmente, um pouco mais lento do que outros métodos, mas o custo-benefício vale a pena. Desempenho em tempo de execução não deve sofrer penalidades desde que globais sejam importados corretamente e, provavelmente, há aumento de velocidade em submódulos, encurtando a cadeia de referência com variáveis ​​locais.

Para fechar sobre Module Patterns, aqui está um exemplo de um submódulo que se carrega dinamicamente a seu parent (criando-se se ele não existe) — o estado privado foi deixado de fora para que o exemplo fique mais conciso, mas incluir isso seria simples.

var UTIL = (function (parent, $) { var my = parent.ajax = parent.ajax || {};  my.get = function (url, params, callback) { // Um pouquinho de trapaça... xD return $.getJSON(url, params, callback); };  // etc  return parent;}(UTIL || {}, jQuery));

Este padrão de código permite que toda uma base de códigos complexa hierárquica seja completamente carregada em paralelo, com sub-módulos e tudo o mais.