Mais cedo ou mais tarde, você precisa usar os resultados abstratos de outros desenvolvedores - ou seja, você confia no código de outras pessoas. Eu gosto de confiar em módulos gratuitos (sem dependência), mas isso é difícil de alcançar. Mesmo aqueles lindos componentes de caixa preta que você criar dependerão de algo mais ou menos. É exatamente isso que torna a injeção de dependência ótima. A capacidade de gerenciar efetivamente dependências agora é absolutamente necessária. Este artigo resume minha exploração do problema e algumas soluções.
1. Objetivo
Imagine que temos dois módulos. O primeiro é responsável pelo serviço de solicitação do Ajax e o segundo é o roteador.
A cópia do código é a seguinte:
var serviço = function () {
return {name: 'Service'};
}
var roteter = function () {
return {nome: 'roteador'};
}
Temos outra função que precisa usar esses dois módulos.
A cópia do código é a seguinte:
var doSomething = function (outros) {
var s = service ();
var r = roteador ();
};
Para torná -lo mais interessante, essa função aceita um argumento. Obviamente, podemos usar completamente o código acima, mas isso obviamente não é flexível o suficiente. E se quisermos usar o Servicexml ou o ServiceJson, ou se precisarmos de alguns módulos de teste. Não podemos resolver o problema editando o corpo da função sozinho. Primeiro, podemos resolver a dependência através dos parâmetros da função. Agora mesmo:
A cópia do código é a seguinte:
var dosomething = function (serviço, roteador, outro) {
var s = service ();
var r = roteador ();
};
Implementamos a funcionalidade que queremos, passando parâmetros adicionais, no entanto, isso traz novos problemas. Imagine se o nosso método do dosomething estiver espalhado em nosso código. Se precisarmos alterar as condições de dependência, é impossível alterar todos os arquivos que chamam a função.
Precisamos de uma ferramenta que possa nos ajudar a fazer essas coisas. Este é o problema que a injeção de dependência tenta resolver. Vamos escrever alguns dos objetivos que nossa solução de injeção de dependência deve alcançar:
Deveríamos ser capazes de registrar dependências
1. A injeção deve aceitar uma função e retornar uma função que precisamos
2. Não podemos escrever muito - precisamos otimizar a bela gramática
3. A injeção deve manter o escopo da função transferida
4. A função passada deve ser capaz de aceitar parâmetros personalizados, não apenas depender das descrições
5. Uma lista perfeita, vamos alcançá -la abaixo.
3. Método requestJS/AMD
Você deve ter ouvido falar de requisitos, que é uma boa opção para resolver a injeção de dependência.
A cópia do código é a seguinte:
define (['serviço', 'roteador'], função (serviço, roteador) {
// ...
});
A idéia é descrever primeiro as dependências necessárias e depois escrever sua função. A ordem dos parâmetros aqui é muito importante. Como mencionado acima, vamos escrever um módulo chamado injetor que possa aceitar a mesma sintaxe.
A cópia do código é a seguinte:
var doSomething = injetor.Resolve (['serviço', 'roteador'], função (serviço, roteador, outro) {
espera (serviço (). nome) .to.be ('serviço');
Espere (Router (). Nome) .to.be ('roteador');
Espere (outros) .to.be ('Outro');
});
doSomething ("outro");
Antes de continuar, devo explicar o conteúdo do corpo da função do que eu uso. método.
Vamos começar nosso módulo injetor, que é um ótimo padrão de singleton, para que funcione bem em diferentes partes do nosso programa.
A cópia do código é a seguinte:
var injector = {
Dependências: {},
registro: function (chave, valor) {
this.dependências [key] = value;
},
Resolva: function (deps, func, escopo) {
}
}
Este é um objeto muito simples, com dois métodos, um para armazenar a propriedade. O que queremos fazer é verificar a matriz de depósito e procurar respostas na variável de dependências. Tudo o que resta é chamar o método .Apply e passar os parâmetros do método FUNC anterior.
A cópia do código é a seguinte:
Resolva: function (deps, func, escopo) {
var args = [];
for (var i = 0; i <deps.Length, d = deps [i]; i ++) {
if (this.dependências [d]) {
args.push (this.dependências [d]);
} outro {
lançar um novo erro ('pode/' t resolver ' + d);
}
}
Return function () {
func.apply (escopo || {}, args.concat (array.prototype.slice.call (argumentos, 0)));
}
}
O escopo é opcional, Array.prototype.slice.call (argumentos, 0) é necessário para converter variáveis de argumentos em matrizes reais. Até agora, não é ruim. Nosso teste passou. O problema dessa implementação é que precisamos escrever as peças necessárias duas vezes e não podemos confundir o pedido deles. Parâmetros personalizados adicionais estão sempre por trás da dependência.
4. Método de reflexão
De acordo com a definição da Wikipedia, a reflexão refere -se à capacidade de um programa de verificar e modificar a estrutura e o comportamento de um objeto em tempo de execução. Simplificando, no contexto do JavaScript, isso se refere especificamente ao código -fonte de um objeto ou função que é lido e analisado. Vamos completar a função do dosomething mencionada no início do artigo. Se você gerar doSomething.ToString () no console. Você receberá a seguinte sequência:
A cópia do código é a seguinte:
"função (serviço, roteador, outro) {
var s = service ();
var r = roteador ();
} "
A sequência retornada por esse método nos permite a capacidade de atravessar os parâmetros e, mais importante, obter seus nomes. Este é realmente o método da Angular para implementar sua injeção de dependência. Eu estava um pouco preguiçoso e interceptei diretamente a expressão regular que recebe os parâmetros no código angular.
A cópia do código é a seguinte:
/^function/s*[^/(]*/(/s*([^/)]*)/)/m
Podemos modificar o código de resolução como este:
A cópia do código é a seguinte:
Resolva: function () {
var func, deps, escopo, args = [], self = this;
func = argumentos [0];
deps = func.toString (). Match (/^function/s*[^/(]*/(/s*([^/)]*)/)/m) [1] .Rplace (//g, '').dividir(',');
escopo = argumentos [1] || {};
Return function () {
var a = array.prototype.slice.call (argumentos, 0);
for (var i = 0; i <deps.Length; i ++) {
var d = deps [i];
args.push (self.dependências [d] && d! = ''? self.dependências [d]: a.shift ());
}
func.apply (escopo || {}, args);
}
}
O resultado de nossa execução de expressões regulares é a seguinte:
A cópia do código é a seguinte:
["função (serviço, roteador, outros)", "serviço, roteador, outros"]
Parece que precisamos apenas do segundo item. Depois de limparmos os espaços e dividir a corda, obtemos a matriz de DEPS. Há apenas uma grande mudança:
A cópia do código é a seguinte:
var a = array.prototype.slice.call (argumentos, 0);
...
args.push (self.dependências [d] && d! = ''? self.dependências [d]: a.shift ());
Voltamos pela matriz de dependências e tentamos obtê -la do objeto Argumentos se encontrarmos itens ausentes. Felizmente, quando a matriz estiver vazia, o método de mudança simplesmente retorna indefinido em vez de lançar um erro (isso é graças à idéia da web). A nova versão do injetor pode ser usada como o seguinte:
A cópia do código é a seguinte:
var doSomething = injetor.Resolve (função (serviço, outro, roteador) {
espera (serviço (). nome) .to.be ('serviço');
Espere (Router (). Nome) .to.be ('roteador');
Espere (outros) .to.be ('Outro');
});
doSomething ("outro");
Não há necessidade de reescrever dependências e sua ordem pode ser interrompida. Ainda funciona, e copiamos com sucesso a magia de Angular.
No entanto, essa prática não é perfeita, o que é um problema muito grande com a injeção do tipo reflexo. A compactação destruirá nossa lógica porque altera o nome do parâmetro e não poderemos manter o relacionamento de mapeamento correto. Por exemplo, doSometing () pode ser assim após a compactação:
A cópia do código é a seguinte:
var doSomething = function (e, t, n) {var r = e (); var i = t ()}
A solução proposta pela equipe angular se parece:
var doSomething = injetor.Resolve (['serviço', 'roteador', função (serviço, roteador) {
}]);
Isso se parece muito com a solução com a qual começamos. Não consegui encontrar uma solução melhor, então decidi combinar os dois. Aqui está a versão final do injetor.
A cópia do código é a seguinte:
var injector = {
Dependências: {},
registro: function (chave, valor) {
this.dependências [key] = value;
},
Resolva: function () {
var func, deps, escopo, args = [], self = this;
if (typeof argumentos [0] === 'string') {
func = argumentos [1];
DEPS = argumentos [0] .Place ( / / g, '') .split (',');
escopo = argumentos [2] || {};
} outro {
func = argumentos [0];
deps = func.toString (). Match (/^function/s*[^/(]*/(/s*([^/)]*)/)/m) [1] .Rplace (//g, '').dividir(',');
escopo = argumentos [1] || {};
}
Return function () {
var a = array.prototype.slice.call (argumentos, 0);
for (var i = 0; i <deps.Length; i ++) {
var d = deps [i];
args.push (self.dependências [d] && d! = ''? self.dependências [d]: a.shift ());
}
func.apply (escopo || {}, args);
}
}
}
Resolva os visitantes aceitam dois ou três parâmetros, se houver dois parâmetros, é realmente o mesmo que foi escrito no artigo anterior. No entanto, se houver três parâmetros, ele converte o primeiro parâmetro e preenche a matriz de DEPS, aqui está um exemplo de teste:
A cópia do código é a seguinte:
var doSomething = injetor.Resolve ('roteador, serviço', função (a, b, c) {
Espere (a (). Nome) .to.be ('roteador');
Espere (b) .to.be ('Outro');
espere (c (). nome) .to.be ('serviço');
});
doSomething ("outro");
Você pode notar que existem duas vírgulas após o primeiro parâmetro - observe que este não é um erro de digitação. Um valor nulo realmente representa o parâmetro "outro" (espaço reservado). Isso mostra como controlamos a ordem dos parâmetros.
5. Injeção direta de escopo
Às vezes, uso a terceira variável de injeção, que envolve o escopo da função de operação (em outras palavras, esse objeto). Portanto, essa variável não é necessária em muitos casos.
A cópia do código é a seguinte:
var injector = {
Dependências: {},
registro: function (chave, valor) {
this.dependências [key] = value;
},
Resolva: function (deps, func, escopo) {
var args = [];
escopo = escopo || {};
for (var i = 0; i <deps.Length, d = deps [i]; i ++) {
if (this.dependências [d]) {
escopo [d] = this.dependências [d];
} outro {
lançar um novo erro ('pode/' t resolver ' + d);
}
}
Return function () {
func.apply (escopo || {}, array.prototype.slice.call (argumentos, 0));
}
}
}
Tudo o que fazemos é adicionar dependências ao escopo. A vantagem disso é que os desenvolvedores não precisam mais escrever parâmetros de dependência;
A cópia do código é a seguinte:
var dosomething = injetor.Resolve (['serviço', 'roteador'], função (outros) {
Espere (this.Service (). Nome) .to.be ('Serviço');
Espere (this.Router (). Nome) .to.be ('roteador');
Espere (outros) .to.be ('Outro');
});
doSomething ("outro");
6. Conclusão
De fato, a maioria de nós usou injeção de dependência, mas não percebemos isso. Mesmo se você não conhece o termo, pode ter usado em seu código um milhão de vezes. Espero que este artigo aprofunde sua compreensão.