Namespacing em JavaScript pode ser considerado algo um pouco nebuloso no começo, mas a organização (e, de certa forma, proteção) de seu código é algo essencial, que deve ser encarado com muita seriedade e feito com bastante atenção. JavaScript não possui uma construção formal de namespace, então é preciso saber como “simular” namespaces para que se possa desfrutar das vantagens que eles oferecem.
Se quer saber mais sobre namespacing em JavaScript e conhecer várias técnicas e possibilidades que a linguagem oferece, continue lendo.
Variáveis globais devem ser reservadas a objetos que têm relevância para todo o sistema e estas devem ser nomeadas para evitar ambiguidades e minimizar o risco de colisões de nomes. Na prática, isso significa que você deve evitar a criação de objetos globais a menos que eles sejam absolutamente necessários. Mas você já sabia disso, certo?!
Você deve evitar a criação de objetos globais a menos que eles sejam absolutamente necessários.
Então, o que você faz sobre isso? A “sabedoria popular” mostra que a melhor estratégia de redução de risco é criar um pequeno número de objetos globais que servirão como namespaces de facto para módulos subjacentes e subsistemas. A seguir, várias abordagens para namespacing em JavaScript interessantes, que você pode usar em seus projetos imediatamente.
Namespacing Estático (Static Namespacing)
O termo “namespacing estático” está sendo usado como um termo genérico para soluções em que a denominação “namespace” consta numa codifição, propriamente dita. É verdade, você pode reatribuir de um namespace para outro, mas o novo namespace vai referenciar os mesmos objetos como se fossem os antigos.
Atribuição Direta (Direct Assignment)
Essa é a abordagem mais básica. Exige mais código, mas é segura e inequívoca.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var myApp = {} myApp.id = 0; myApp.next = function() { return myApp.id++; } myApp.reset = function() { myApp.id = 0; } window.console && console.log( myApp.next(), myApp.next(), myApp.reset(), myApp.next() ); // 0, 1, undefined, 0 |
Você poderia tornar a manutenção futura um pouco mais fácil usando this
para fazer referência a propriedades irmãs, mas isso é um pouco arriscado, já que não há nada que impeça suas funções no namespace de serem reatribuídas:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var myApp = {} myApp.id = 0; myApp.next = function() { return this.id++; } myApp.reset = function() { this.id = 0; } myApp.next(); // 0 myApp.next(); // 1 var getNextId = myApp.next; getNextId(); // NaN whoops! |
Notação de Objeto Literal (Object Literal Notation)
Nesta abordagem, é preciso fazer referência ao nome de namespace apenas uma vez, então, caso seja preciso alterar o nome mais tarde, isso é um pouco mais fácil de se conseguir (supondo que você não tenha referenciado o namespace muitas vezes). Há, ainda, o perigo de que o valor de this
possa ser uma surpresa – mas é um pouco mais seguro assumir que os objetos definidos dentro de uma construção de objeto literal não serão reatribuídos.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var myApp = { id: 0, next: function() { return this.id++; }, reset: function() { this.id = 0; } } window.console && console.log( myApp.next(), myApp.next(), myApp.reset(), myApp.next() ) // 0, 1, undefined, 0 |
Padrão de Módulo (Module Pattern)
Hoje em dia é bastante comum encontrar por aí o Padrão de Módulo (Module Pattern) em código JavaScript. A lógica é protegida do escopo global por uma função wrapper (geralmente numa IIFE) que retorna um objeto que representa a interface pública do módulo. Por imediatamente invocar a função e atribuir o resultado a uma variável de namespace, a API do módulo é “trancada” nesse namespace.
Além disso, todas as variáveis não incluídas no valor de retorno permanecerão privadas, visíveis apenas para as funções públicas que lhes referenciam.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
var myApp = (function() { var id = 0; return { next: function() { return id++; }, reset: function() { id = 0; } }; })(); window.console && console.log( myApp.next(), myApp.next(), myApp.reset(), myApp.next() ) // 0, 1, undefined, 0 |
Namespacing Dinâmico (Dynamic Namespacing)
Esta seção também poderia muito bem ser chamada de “injeção de namespace” (ou namespace injection). O namespace é representado por um proxy que é referenciado diretamente dentro do wrapper da função – o que significa que não é mais preciso agrupar valores para o return
no namespace. Isso faz com que a definição de namespace seja mais flexível e torna muito fácil ter várias instâncias independentes de um módulo existente em namespaces separados (ou até mesmo no contexto global).
Namespacing dinâmico suporta todas as características do Padrão de Módulo com a vantagem adicional de ser intuitivo e de fácil leitura.
Passando argumento de namespace
Aqui o namespace é simplesmente passado como um argumento de uma Expressão de Função Imediatamente Invocada. A variável id
é privada porque não é atribuída ao contexto.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var myApp = {}; (function(context) { var id = 0; context.next = function() { return id++; }; context.reset = function() { id = 0; } })(myApp); window.console && console.log( myApp.next(), myApp.next(), myApp.reset(), myApp.next() ) // 0, 1, undefined, 0 |
É possível até mesmo definir o contexto para o objeto global – com a mudança de uma palavra! Este é um grande trunfo para os fornecedores da bibliotecas (vendors), que podem envolver suas features numa IIFE e permitir que o programador decida se elas devem ser globais ou não (John Resig foi um dos primeiros a adotar esse conceito quando escreveu a jQuery).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var myApp = {}; (function(context) { var id = 0; context.next = function() { return id++; }; context.reset = function() { id = 0; } })(this); window.console && console.log( next(), next(), reset(), next() ) // 0, 1, undefined, 0 |
this
como proxy de namespace
O artigo “My Favorite JavaScript Design Pattern” aparentemente foi mal compreendido por muitos que pensaram que ele poderia simplesmente recorrer ao Module Pattern. O artigo mostra várias técnicas (o que provavelmente contribuiu para essa confusão), mas, em essência, é muito interessante e dá ensejo ao desenvolvimento de muita coisa boa relacionada a namespacing.
A beleza do padrão é que ele simplesmente usa a linguagem como foi projetada – nada mais, nada menos, sem truques, sem firulas. Além disso, já que o namespace é injetado através da palavra-chave this
(que é estática dentro de um determinado contexto de execução), ele não pode ser acidentalmente modificado.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var myApp = {}; (function() { var id = 0; this.next = function() { return id++; }; this.reset = function() { id = 0; } }).apply(myApp); window.console && console.log( myApp.next(), myApp.next(), myApp.reset(), myApp.next() ); // 0, 1, undefined, 0 |
Ainda melhor, a API de apply
(e call
) provê separação natural entre contexto e argumentos; então, fica simples passar argumentos adicionais para o criador do módulo.
O exemplo a seguir demonstra isso e também mostra como executar o módulo de forma independente através de múltiplos namespaces:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
var subsys1 = {}, subsys2 = {}; var nextIdMod = function(startId) { var id = startId || 0; this.next = function() { return id++; }; this.reset = function() { id = 0; } }; nextIdMod.call(subsys1); nextIdMod.call(subsys2,1000); window.console && console.log( subsys1.next(), subsys1.next(), subsys2.next(), subsys1.reset(), subsys2.next(), subsys1.next() ) // 0, 1, 1000, undefined, 1001, 0 |
E, se for preciso um gerador global de id
:
1 2 3 4 5 6 7 8 |
nextIdMod(); window.console && console.log( next(), next(), reset(), next() ) // 0, 1, undefined, 0 |
Mas essa ferramenta de gerar de id
s mostrada como exemplo não faz jus a todo o potencial desse padrão. Envolvendo uma biblioteca inteira e usando a palavra-chave this
como um suporte para o namespace, é fácil executar a biblioteca em qualquer contexto que se queira (incluindo o contexto global).
1 2 3 4 5 6 7 8 |
// Código de biblioteca var protoQueryMooJo = function() { // [...] } // Seu código var thirdParty = {}; protoQueryMooJo.apply(thirdParty); |
Considerações finais sobre Namespacing em JavaScript
É uma prática comum tentar evitar namespaces aninhados. Eles são mais difíceis de seguir (para ambos, humanos e computadores) e vão encher seu código de confusão desnecessária. Como Peter Michaux aponta, namespaces aninhados podem ser um legado de desenvolvedores Java nostálgicos tentando recriar aquelas cadeias longas de pacotes que eles conheciam e amavam…
É possível “propagar” um único namespace através de diversos arquivos .js
(embora apenas por injeção de namespace ou atribuição direta de cada variável), contudo, é preciso ter cuidado com as dependências. Além disso, “ligar” um namespace a um arquivo pode ajudar o leitor a navegar mais facilmente pelo código.
Uma vez que JavaScript não possui uma construção formal de namespace, isso abre margem para vários tipos de soluções criativas para resolver a questão. Este artigo mostrou apenas algumas delas e, evidentemente, as melhores podem não terem sido abordadas, dada a vasta contribuição da comunidade a cada dia.
De qualquer forma, estas são algumas das mais conhecidas técnicas para namespacing em JavaScript e, garantimos, estudando um pouco além e sabendo aplicar corretamente em seus projetos, certamente você terá um incremento de qualidade de código considerável!