Tarde o temprano debe usar los resultados abstractos de otros desarrolladores, es decir, confía en el código de otras personas. Me gusta confiar en módulos gratuitos (sin dependencia), pero eso es difícil de lograr. Incluso esos hermosos componentes de caja negra que cree dependerán de algo más o menos. Esto es exactamente lo que hace que la inyección de dependencia sea grande. La capacidad de gestionar efectivamente las dependencias ahora es absolutamente necesaria. Este artículo resume mi exploración del problema y algunas soluciones.
1. Objetivo
Imagina que tenemos dos módulos. El primero es responsable del servicio de solicitud AJAX, y el segundo es el enrutador.
La copia del código es la siguiente:
VAR Service = function () {
return {nombre: 'servicio'};
}
var enrutador = function () {
return {nombre: 'enrutador'};
}
Tenemos otra función que necesita usar estos dos módulos.
La copia del código es la siguiente:
var dosomthing = function (otro) {
var s = servicio ();
var r = router ();
};
Para hacerlo más interesante, esta función acepta un argumento. Por supuesto, podemos usar el código anterior por completo, pero obviamente esto no es lo suficientemente flexible. ¿Qué pasa si queremos usar ServiceXML o ServiceJson, o qué pasa si necesitamos algunos módulos de prueba? No podemos resolver el problema editando el cuerpo de la función solo. Primero, podemos resolver la dependencia a través de los parámetros de la función. Ahora mismo:
La copia del código es la siguiente:
var dosomthing = function (servicio, enrutador, otro) {
var s = servicio ();
var r = router ();
};
Implementamos la funcionalidad que queremos al pasar parámetros adicionales, sin embargo, esto trae nuevos problemas. Imagínese si nuestro método de dos tendos está disperso en nuestro código. Si necesitamos cambiar las condiciones de dependencia, es imposible para nosotros cambiar todos los archivos llamando a la función.
Necesitamos una herramienta que pueda ayudarnos a hacer estas cosas. Este es el problema que la inyección de dependencia intenta resolver. Escriba algunos de los objetivos que nuestra solución de inyección de dependencia debería lograr:
Deberíamos poder registrar dependencias
1. La inyección debe aceptar una función y devolver una función que necesitamos
2. No podemos escribir demasiado, necesitamos racionalizar la hermosa gramática
3. La inyección debe mantener el alcance de la función transferida
4. La función aprobada debe poder aceptar parámetros personalizados, no solo depender de las descripciones
5. Una lista perfecta, loglemos a continuación.
3. Método requerido/AMD
Es posible que haya oído hablar de Requirjs, que es una buena opción para resolver la inyección de dependencia.
La copia del código es la siguiente:
Define (['servicio', 'enrutador'], function (servicio, enrutador) {
// ...
});
La idea es describir primero las dependencias requeridas y luego escribir su función. El orden de los parámetros aquí es muy importante. Como se mencionó anteriormente, escribamos un módulo llamado inyector que pueda aceptar la misma sintaxis.
La copia del código es la siguiente:
var dosomthing = injector.resolve (['servicio', 'enrutador'], function (servicio, enrutador, otro) {
esperar (servicio (). name) .to.be ('servicio');
esperar (enrutador (). name) .to.be ('enrutador');
esperar (otro) .to.be ('otro');
});
Dosomething ("otro");
Antes de continuar, debo explicar claramente el contenido del cuerpo de la función de thantos. método.
Comencemos nuestro módulo de inyector, que es un gran patrón de singleton, por lo que funciona bien en diferentes partes de nuestro programa.
La copia del código es la siguiente:
var inyector = {
Dependencias: {},
registrar: function (clave, valor) {
this.dependencies [clave] = valor;
},
Resolve: function (DEPS, FUNC, ALCAN) {
}
}
Este es un objeto muy simple, con dos métodos, uno para almacenar la propiedad. Lo que queremos hacer es verificar la matriz DEPS y buscar respuestas en la variable de dependencias. Todo lo que queda es llamar al método .Apply y pasar los parámetros del método FUNC anterior.
La copia del código es la siguiente:
Resolve: function (DEPS, FUNC, ALCAN) {
var args = [];
para (var i = 0; i <deps.length, d = deps [i]; i ++) {
if (this.dependencies [d]) {
args.push (this.dependencias [d]);
} demás {
arrojar un nuevo error ('Can/' t resolve ' + d);
}
}
Función de retorno () {
FunC.Apply (alcance || {}, args.concat (array.prototype.slice.call (argumentos, 0)));
}
}
El alcance es opcional, array.prototype.slice.call (argumentos, 0) es necesario para convertir las variables de argumentos en matrices reales. Hasta ahora no está mal. Nuestra prueba pasó. El problema con esta implementación es que necesitamos escribir las partes requeridas dos veces y no podemos confundir su pedido. Los parámetros personalizados adicionales siempre están detrás de la dependencia.
4. Método de reflexión
Según la definición de Wikipedia, la reflexión se refiere a la capacidad de un programa para verificar y modificar la estructura y el comportamiento de un objeto en tiempo de ejecución. En pocas palabras, en el contexto de JavaScript, esto se refiere específicamente al código fuente de un objeto o función que se lee y analiza. Completemos la función de thanting mencionada al comienzo del artículo. Si emite dosomething.toString () en la consola. Obtendrá la siguiente cadena:
La copia del código es la siguiente:
"función (servicio, enrutador, otro) {
var s = servicio ();
var r = router ();
} "
La cadena devuelta por este método nos da la capacidad de atravesar los parámetros y, lo que es más importante, para obtener sus nombres. Este es en realidad el método de Angular para implementar su inyección de dependencia. Era un poco perezoso e intercepté directamente la expresión regular que obtiene los parámetros en el código angular.
La copia del código es la siguiente:
/^function/s*[^/(]*/(/s*([^/)]*)/)/m
Podemos modificar el código de resolución como este:
La copia del código es la siguiente:
resolve: functer () {
var func, deps, alcance, args = [], self = this;
func = argumentos [0];
DEPS = FUNC.ToString (). Match (/^function/s*[^/(]*/(/s*([^/)]*)/m) [1] .replace (//g, '').dividir(',');
alcance = argumentos [1] || {};
Función de retorno () {
var a = array.prototype.slice.call (argumentos, 0);
para (var i = 0; i <deps.length; i ++) {
var d = deps [i];
args.push (self.dependencies [d] && d! = ''?
}
FunC.Apply (alcance || {}, args);
}
}
El resultado de nuestra ejecución de expresiones regulares es el siguiente:
La copia del código es la siguiente:
["Función (servicio, enrutador, otro)", "Servicio, enrutador, otro"]
Parece que solo necesitamos el segundo elemento. Una vez que limpiamos los espacios y dividimos la cadena, obtenemos la matriz DEPS. Solo hay un gran cambio:
La copia del código es la siguiente:
var a = array.prototype.slice.call (argumentos, 0);
...
args.push (self.dependencies [d] && d! = ''?
Realizamos la matriz de dependencias e intentamos obtenerlo del objeto Argumentos si encontramos elementos faltantes. Afortunadamente, cuando la matriz está vacía, el método de cambio simplemente devuelve indefinido en lugar de lanzar un error (esto es gracias a la idea de la web). La nueva versión de inyector se puede usar como la siguiente:
La copia del código es la siguiente:
var dosomthing = injector.resolve (function (servicio, otro, enrutador) {
esperar (servicio (). name) .to.be ('servicio');
esperar (enrutador (). name) .to.be ('enrutador');
esperar (otro) .to.be ('otro');
});
Dosomething ("otro");
No hay necesidad de reescribir dependencias y su orden puede ser interrumpido. Todavía funciona, y copiamos con éxito la magia de Angular.
Sin embargo, esta práctica no es perfecta, lo cual es un gran problema con la inyección de tipo reflejo. La compresión destruirá nuestra lógica porque cambia el nombre del parámetro y no podremos mantener la relación de mapeo correcta. Por ejemplo, Dosometing () podría verse así después de la compresión:
La copia del código es la siguiente:
var dosomthing = function (e, t, n) {var r = e (); var i = t ()}
La solución propuesta por el equipo angular parece:
var doSomething = injector.resolve (['servicio', 'enrutador', function (servicio, enrutador) {
}]);
Esto se parece mucho a la solución con la que comenzamos. No pude encontrar una mejor solución, así que decidí combinar ambos. Aquí está la versión final del inyector.
La copia del código es la siguiente:
var inyector = {
Dependencias: {},
registrar: function (clave, valor) {
this.dependencies [clave] = valor;
},
resolve: functer () {
var func, deps, alcance, args = [], self = this;
if (typeof argumentos [0] === 'string') {
func = argumentos [1];
deps = argumentos [0] .replace ( / / g, '') .split (',');
alcance = argumentos [2] || {};
} demás {
func = argumentos [0];
DEPS = FUNC.ToString (). Match (/^function/s*[^/(]*/(/s*([^/)]*)/m) [1] .replace (//g, '').dividir(',');
alcance = argumentos [1] || {};
}
Función de retorno () {
var a = array.prototype.slice.call (argumentos, 0);
para (var i = 0; i <deps.length; i ++) {
var d = deps [i];
args.push (self.dependencies [d] && d! = ''?
}
FunC.Apply (alcance || {}, args);
}
}
}
Resolver Los visitantes aceptan dos o tres parámetros, si hay dos parámetros, en realidad es lo mismo que se escribió en el artículo anterior. Sin embargo, si hay tres parámetros, convierte el primer parámetro y llena la matriz DEPS, aquí hay un ejemplo de prueba:
La copia del código es la siguiente:
var dosomething = injector.resolve ('enrutador ,, servicio', función (a, b, c) {
esperar (a (). nombre) .to.be ('enrutador');
esperar (b) .to.be ('otro');
esperar (c (). name) .to.be ('servicio');
});
Dosomething ("otro");
Puede notar que hay dos comas después del primer parámetro: tenga en cuenta que esto no es un error tipográfico. Un valor nulo en realidad representa el "otro" parámetro (marcador de posición). Esto muestra cómo controlamos el orden de los parámetros.
5. Inyección directa de alcance
A veces uso la tercera variable de inyección, que implica el alcance de la función de operación (en otras palabras, este objeto). Por lo tanto, esta variable no es necesaria en muchos casos.
La copia del código es la siguiente:
var inyector = {
Dependencias: {},
registrar: function (clave, valor) {
this.dependencies [clave] = valor;
},
Resolve: function (DEPS, FUNC, ALCAN) {
var args = [];
alcance = alcance || {};
para (var i = 0; i <deps.length, d = deps [i]; i ++) {
if (this.dependencies [d]) {
alcance [d] = this.dependencies [d];
} demás {
arrojar un nuevo error ('Can/' t resolve ' + d);
}
}
Función de retorno () {
FunC.Apply (alcance || {}, array.prototype.slice.call (argumentos, 0));
}
}
}
Todo lo que hacemos es agregar dependencias al alcance. La ventaja de esto es que los desarrolladores ya no tienen que escribir parámetros de dependencia;
La copia del código es la siguiente:
var dosomthing = injector.resolve (['servicio', 'enrutador'], function (otros) {
Espere (this.service (). name) .to.be ('servicio');
esperar (this.router (). name) .to.be ('enrutador');
esperar (otro) .to.be ('otro');
});
Dosomething ("otro");
6. Conclusión
De hecho, la mayoría de nosotros hemos usado la inyección de dependencia, pero no nos damos cuenta. Incluso si no sabe el término, es posible que lo haya usado en su código un millón de veces. Espero que este artículo profundice su comprensión.