Front End

12 ago, 2011

Namespacing no JavaScript

Publicidade

Variáveis globais deveriam ser reservadas para objetos que têm
relevância para o tamanho do sistema, e deveriam ser nomeadas para evitar ambiguidade
e para minimizar o risco de nomear colisões. Na prática, isso significa que você
deveria evitar criar objetos globais, a não ser que eles sejam absolutamente
necessários.

Mas, ei, você já sabia disso…

Então, o que você faz a respeito disso? A sabedoria convencional
nos diz que a melhor estratégia de moderação global é criar um pequeno número
de objetos globais que irá servir como os nasmespaces de fato para módulos
subjacentes e sub-sistemas. Eu vou explorar diversas abordagens para o
namespacing, culminando em uma solução flexível, segura e elegante que eu baseei
em um artigo de James Edwards.

Namespacing estático

Estou usando namespacing estático como um termo que engloba
soluções nas quais o rótulo namespacing é efetivamente codificado. É verdade, você
poderia redirecionar um namespace para outro, mas o novo namespace irá
referenciar os mesmos objetos que o antigo.

1. Através de direct assignment

A abordagem mais básica. É prolixa, e se você quiser
renomear o namespace, você tem um trabalho em mãos. No entanto, é segura e inequívoca.

01	var myApp = {}
02
03 myApp.id = 0;
04
05 myApp.next = function() {
06 return myApp.id++;
07 }
08
09 myApp.reset = function() {
10 myApp.id = 0;
11 }
12
13 window.console && console.log(
14 myApp.next(),
15 myApp.next(),
16 myApp.reset(),
17 myApp.next()
18 ); //0, 1, undefined, 0

Você poderia deixar uma manutenção futura um pouco mais fácil ao usar this para referenciar propriedades irmãs – mas isso é um pouco arriscado, uma
vez que não há nada para parar suas funções namespaced de serem reatribuídas:

01	var myApp = {}
02
03 myApp.id = 0;
04
05 myApp.next = function() {
06 return this.id++;
07 }
08
09 myApp.reset = function() {
10 this.id = 0;
11 }
12
13 myApp.next(); //0
14 myApp.next(); //1
15 var getNextId = myApp.next;
16 getNextId(); //NaN whoops!

2. Usando notação literal de objeto

Agora somente precisamos nos referir ao nome namespace
uma vez, então mudar o nome depois é um pouco mais fácil (pressupondo que você
ainda não referenciou o namespace muitas vezes). Ainda existe o perigo de seu
valor this ser uma surpresa – mas é um pouco mais seguro pressupor que objetos
definidos dentro do construtor do objeto literal não serão reatribuídos.

01	var myApp = {
02
03 id: 0,
04
05 next: function() {
06 return this.id++;
07 },
08
09 reset: function() {
10 this.id = 0;
11 }
12 }
13 window.console && console.log(
14 myApp.next(),
15 myApp.next(),
16 myApp.reset(),
17 myApp.next()
18 ) //0, 1, undefined, 0

3. O módulo padrão

Eu tenho usado o módulo padrão mais vezes
esses dias. A lógica é blindada do escopo global por uma function wrapper
(normalmente se autoinvocando), o que retorna um objeto representando a
interface pública do módulo. Ao invocar imediatamente a função e atribuindo o
resultado para um namespace variável, trancamos a API do módulo no namespace.
Adicionalmente, quaisquer variáveis não incluídas no valor retornado irão se
manter sempre privadas, somente visíveis para as funções públicas que as
referenciam. 

01	var myApp = (function() {
02
03 var id= 0;
04
05 return {
06 next: function() {
07 return id++;
08 },
09
10 reset: function() {
11 id = 0;
12 }
13 };
14 })();
15
16 window.console && console.log(
17 myApp.next(),
18 myApp.next(),
19 myApp.reset(),
20 myApp.next()
21 ) //0, 1, undefined, 0

Como o objeto literal
acima, o namespace recebido pode ser facilmente trocado, mas existem outras
vantagens: a notação de objeto literal é rígida – tudo que gira em torno das
requisições das propriedades devem ser inicializadas e os valores da
propriedade, sem espaço para suportar lógica. Além disso, todas as propriedades
devem ser iniciadas, e os valores da propriedade não conseguem atravessar
facilmente a referência uma da outra (então, por exemplo, closures internas não
são possíveis). O padrão modular não sofre nenhuma dessas limitações, e nos dá
o benefício adicional da privacidade.

Namespacing dinâmico

Poderíamos chamar essa
sessão de namespace injection. O namespace é representado por um proxy
que é referenciado diretamente dentro da função wrapper – o que significa que
não precisamos mais agrupar um valor de retorno para designar ao namespace.
Isso deixa a definição namespace mais flexível e facilita a existência de instâncias
múltiplas independentes de um módulo existente em namespaces separados (ou até
no contexto global). Namespacing dinâmico suporta todos os recursos do padrão
modular com a vantagem de ser intuitivo e legível.

4. Forneça um argumento namespace

Aqui nós simplesmente passamos um namespace como um
argumento para uma função autoinvocativa. A  variável id é privada porque não é
atribuída a context.

01	var myApp = {};
02 (function(context) {
03 var id = 0;
04
05 context.next = function() {
06 return id++;
07 };
08
09 context.reset = function() {
10 id = 0;
11 }
12 })(myApp);
13
14 window.console && console.log(
15 myApp.next(),
16 myApp.next(),
17 myApp.reset(),
18 myApp.next()
19 ) //0, 1, undefined, 0

Podemos até configurar o contexto para o objeto global (com uma mudança
de palavra!). Esse é um grande ativo para os vendedores de bibliotecas – que
podem envolver seus recursos em uma função autoinvocatória e deixar para o
usuário decidir se elas deveriam ser globais ou não (John Resig foi early adopter desse conceito quando escreveu o JQuery).

01	var myApp = {};
02 (function(context) {
03 var id = 0;
04
05 context.next = function() {
06 return id++;
07 };
08
09 context.reset = function() {
10 id = 0;
11 }
12 })(this);
13
14 window.console && console.log(
15 next(),
16 next(),
17 reset(),
18 next()
19 ) //0, 1, undefined, 0

5. Use this como um proxy do namespace

Um post recente de James Edwards me despertou o interesse. Meu design JavaScript favorito de patterns
era aparentemente mal compreendido por muitos comentaristas, que pensaram que
ele poderia recorrer à pattern modular. O artigo discorre sobre várias técnicas
(o que provavelmente contribui para a confusão do leitor), mas no fundo é de
certa forma brilhante, por isso eu o renovei e o apresentei aqui como uma
ferramenta de namespacing.
A beleza do padrão é que ele simplesmente usa a
linguagem como foi projetado – nada mais, nada menos, sem truques, sem
doçura. Além disso, como o namespace é injetado através da palavra-chave this (que
é estática quando em um contexto de execução), ele não pode ser acidentalmente
modificado.

01	var myApp = {};
02 (function() {
03 var id = 0;
04
05 this.next = function() {
06 return id++;
07 };
08
09 this.reset = function() {
10 id = 0;
11 }
12 }).apply(myApp);
13
14 window.console && console.log(
15 myApp.next(),
16 myApp.next(),
17 myApp.reset(),
18 myApp.next()
19 ); //0, 1, undefined, 0

Melhor ainda, as APIs apply (e call) fornecem uma separação natural do
contexto e dos argumentos – então passar argumentos adicionais para o módulo
criador é bastante limpo. O exemplo a seguir demonstra exatamente isso, e
também mostra como executar o módulo independentemente de namespaces
múltiplos:

01	var subsys1 = {}, subsys2 = {};
02 var nextIdMod = function(startId) {
03 var id = startId || 0;
04
05 this.next = function() {
06 return id++;
07 };
08
09 this.reset = function() {
10 id = 0;
11 }
12 };
13
14 nextIdMod.call(subsys1);
15 nextIdMod.call(subsys2,1000);
16
17 window.console && console.log(
18 subsys1.next(),
19 subsys1.next(),
20 subsys2.next(),
21 subsys1.reset(),
22 subsys2.next(),
23 subsys1.next()
24 ) //0, 1, 1000, undefined, 1001, 0

Claro que se queremos um gerador global id, é moleza…

1	nextIdMod();   
2
3 window.console && console.log(
4 next(),
5 next(),
6 reset(),
7 next()
8 ) //0, 1, undefined, 0

A ferramenta gerador id que temos usado como exemplo não faz justiça a
todo o potencial desse padrão. Ao envolver toda a biblioteca e usar
essa palavra-chave como um suporte para o namespace, queremos facilitar para o
usuário a execução da biblioteca em qualquer contexto que ele escolher
(incluindo o contexto global).

1	//library code
2 var protoQueryMooJo = function() {
3 //everything
4 }
5
6 //user code
7 var thirdParty = {};
8 protoQueryMooJo.apply(thirdParty);

Outras considerações

Eu tento evitar
nested namespaces. Eles são difíceis de seguir (para humanos e computadores) e sujarão o código. Profundos
nested namespaces podem ser um legado dos desenvolvedores nostálgicos de Java,
tentando recriar as longas cadeia de pacotes que eles conheciam e amavam. É
possível passar um único namespace através de arquivos .js (apesar de que
somente através de namespace injection ou de direct assignment de todas as
variáveis). No entanto, você deve ser cuidadoso com as dependências. Além disso,
fazer a ligação de um namespace a um arquivo pode ajudar o leitor a navegar
mais facilmente pela linha de código.

Como o JavaScript não tem uma construção formal de
namespace, existe um rico panorama de soluções criadas em casa por aí. Esta análise detalha apenas algumas delas, e devem existir técnicas melhores que eu
não apresentei. 

Leitura Complementar

James Edwards: My Favorite JavaScript Design Pattern
Peter Michaux: JavaScript Namespacing

?

Texto original
disponível em http://javascriptweblog.wordpress.com/2010/12/07/namespacing-in-javascript/