El mundo de la programación de computadoras es en realidad un proceso de abstraer constantemente piezas simples y organizar estas abstracciones. JavaScript no es una excepción. Cuando usamos JavaScript para escribir aplicaciones, ¿usamos código escrito por otros, como algunas bibliotecas o marcos de código abierto famosas? A medida que nuestros proyectos crecen, más y más módulos debemos confiar. En este momento, cómo organizar de manera efectiva estos módulos se ha convertido en un tema muy importante. La inyección de dependencia resuelve el problema de cómo organizar de manera efectiva los módulos de dependencia del código. Es posible que haya oído hablar del término "inyección de dependencia" en algunos marcos o bibliotecas, como los famosos AngularJ de marco front-end. La inyección de dependencia es una de las características muy importantes. Sin embargo, la inyección de dependencia no es nada nuevo, ha existido durante mucho tiempo en otros lenguajes de programación como PHP. Al mismo tiempo, la inyección de dependencia no es tan complicada como se imagina. En este artículo, aprenderemos el concepto de inyección de dependencia en JavaScript y explicaremos de una manera fácil de entender cómo escribir el código de "estilo de inyección de dependencia".
Configuración de objetivo
Supongamos que tenemos dos módulos ahora. La función del primer módulo es enviar solicitudes AJAX, mientras que la función del segundo módulo se utiliza como enrutamiento.
La copia del código es la siguiente:
VAR Service = function () {
return {nombre: 'servicio'};
}
var enrutador = function () {
return {nombre: 'enrutador'};
}
En este momento, escribimos una función que necesita usar los dos módulos mencionados anteriormente:
La copia del código es la siguiente:
var dosomthing = function (otro) {
var s = servicio ();
var r = router ();
};
Aquí, para que nuestro código sea más interesante, este parámetro necesita recibir varios parámetros más. Por supuesto, podemos usar el código anterior, pero no importa desde ningún aspecto, el código anterior parece un poco menos flexible. ¿Qué debemos hacer si el nombre del módulo que necesitamos usar se convierte en ServiceXml o ServiceJson? ¿O qué pasa si queremos usar algunos módulos falsos para fines de prueba? En este momento, no podemos simplemente editar la función en sí. Por lo tanto, lo primero que debemos hacer es pasar el módulo dependiente como parámetros a la función, el código es el siguiente:
La copia del código es la siguiente:
var dosomthing = function (servicio, enrutador, otro) {
var s = servicio ();
var r = router ();
};
En el código anterior, pasamos los módulos que necesitamos por completo. Pero esto plantea un nuevo problema. Supongamos que llamamos al método de thanting en la parte hermano del código. En este momento, ¿qué debemos hacer si necesitamos una tercera dependencia? En este momento, no es una forma sabia de editar todo el código de llamada de función. Así que necesitamos un código para ayudarnos a hacer esto. Este es el problema que el inyector de dependencia está tratando de resolver. Ahora podemos establecer nuestros objetivos:
1. Deberíamos poder registrar dependencias
2. El inyector de dependencia debe recibir una función y luego devolver una función que pueda obtener los recursos requeridos
3. El código no debe ser complicado, pero debe ser simple y amigable
4. El inyector de dependencia debe mantener el alcance de la función pasada
5. La función aprobada debe poder recibir parámetros personalizados, no solo las dependencias descritas
Método de requerir JS/AMD
Quizás haya oído hablar de los famosos necesidades, que es una biblioteca que puede resolver bien el problema de inyección de dependencia:
La copia del código es la siguiente:
Define (['servicio', 'enrutador'], function (servicio, enrutador) {
// ...
});
La idea de requirirjs es que primero debemos describir los módulos requeridos y luego escribir sus propias funciones. Entre ellos, el orden de los parámetros es muy importante. Supongamos que necesitamos escribir un módulo llamado inyector que pueda implementar una sintaxis similar.
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, una cosa a tener en cuenta es que en el cuerpo de funciones de Dosomething, usamos la biblioteca de afirmación que espera.js para garantizar la corrección del código. Aquí hay un poco similar a TDD (desarrollo basado en pruebas).
Ahora comenzamos oficialmente a escribir nuestro módulo de inyector. Primero debería ser un monómero para que pueda tener la misma funcionalidad en cada parte de nuestra aplicación.
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 objeto es muy simple, con solo dos funciones y una variable para fines de almacenamiento. Lo que debemos hacer es verificar la matriz DEPS y luego buscar la respuesta en las variables de dependencias. El resto es usar el método .Apply para llamar a la variable FUNC que pasamos:
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)));
}
}
Si necesita especificar un alcance, el código anterior también se ejecutará normalmente.
En el código anterior, el papel de array.prototype.slice.call (argumentos, 0) es convertir la variable de argumentos en una matriz real. Hasta ahora, nuestro código ha pasado perfectamente la prueba. Pero el problema aquí es que tenemos que escribir los módulos requeridos dos veces, y no podemos organizarlos arbitrariamente. Los parámetros adicionales siempre son seguidos por todas las dependencias.
Método de reflexión
Según la explicación en Wikipedia, la reflexión se refiere al hecho de que un objeto puede modificar su propia estructura y comportamiento durante la ejecución. En JavaScript, simplemente coloque la capacidad de leer el código fuente de un objeto y analizar el código fuente. O vuelve a nuestro método de tendhos, si llama al método dosomething.toString (), puede obtener la siguiente cadena:
La copia del código es la siguiente:
"función (servicio, enrutador, otro) {
var s = servicio ();
var r = router ();
} "
De esta manera, mientras usemos este método, podemos obtener fácilmente los parámetros que queremos y, lo que es más importante, sus nombres. Este es también el método utilizado por AngularJS para implementar la inyección de dependencia. En el código AngularJS, podemos ver la siguiente expresión regular:
La copia del código es la siguiente:
/^function/s*[^/(]*/(/s*([^/)]*)/)/m
Podemos modificar el método de resolución al siguiente código:
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, '') .split (',');
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);
}
}
Usamos la expresión regular anterior para que coincida con la función que definimos, y podemos obtener el siguiente resultado:
La copia del código es la siguiente:
["Función (servicio, enrutador, otro)", "Servicio, enrutador, otro"]
En este punto, solo necesitamos el segundo elemento. Pero una vez que eliminamos los espacios adicionales y cortamos la cadena, obtenemos la matriz DEPS. El siguiente código es la parte que modificamos:
La copia del código es la siguiente:
var a = array.prototype.slice.call (argumentos, 0);
...
args.push (self.dependencies [d] && d! = ''?
En el código anterior, atravesamos el proyecto de dependencia, si faltan elementos, si hay piezas faltantes en el proyecto de dependencia, lo obtenemos del objeto de argumentos. Si una matriz es una matriz vacía, el uso del método de desplazamiento solo volverá indefinido sin lanzar un error. Hasta ahora, la nueva versión de inyector se ve así:
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");
En el código anterior, podemos confundir el orden de dependencias a voluntad.
Pero nada es perfecto. Existe un problema muy grave con la inyección de dependencia de los métodos de reflexión. Se produce un error cuando se simplifica el código. Esto se debe a que durante la simplificación del código, el nombre del parámetro cambia, lo que hará que las dependencias se resuelvan. Por ejemplo:
La copia del código es la siguiente:
var dosomthing = function (e, t, n) {var r = e (); var i = t ()}
Entonces necesitamos la siguiente solución, como en AngularJS:
La copia del código es la siguiente:
var doSomething = injector.resolve (['servicio', 'enrutador', function (servicio, enrutador) {
}]);
Esto es muy similar a la solución AMD que vi al principio, por lo que podemos integrar los dos métodos anteriores, y el código final es el siguiente:
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, '') .split (',');
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);
}
}
}
Esta versión del método de resolución puede aceptar dos o tres parámetros. Aquí hay un código 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");
Es posible que haya notado que no hay nada entre dos comas, y eso no es un error. Esta vacante se deja para el otro parámetro. Así es como controlamos el orden de los parámetros.
Conclusión
En el contenido anterior, introdujimos varios métodos de inyección de dependencia en JavaScript. Espero que este artículo pueda ayudarlo a comenzar a usar la técnica de inyección de dependencia y escribir el código de estilo de inyección de dependencia.