Früher oder später müssen Sie die abstrakten Ergebnisse anderer Entwickler verwenden - das heißt, Sie verlassen sich auf den Code anderer Personen. Ich stütze mich gerne auf freie (abhängig freie) Module, aber das ist schwer zu erreichen. Sogar die schönen Black Box -Komponenten, die Sie erstellen, hängen von etwas mehr oder weniger ab. Genau das macht die Abhängigkeitsinjektion großartig. Die Fähigkeit, Abhängigkeiten effektiv zu verwalten, ist jetzt unbedingt erforderlich. Dieser Artikel fasst meine Erforschung des Problems und einige Lösungen zusammen.
1. Ziel
Stellen Sie sich vor, wir haben zwei Module. Der erste ist für den AJAX -Anforderungsdienst verantwortlich, und der zweite ist der Router.
Die Codekopie lautet wie folgt:
var service = function () {
return {name: 'service'};
}
var Router = function () {
return {name: 'Router'};
}
Wir haben eine andere Funktion, die diese beiden Module verwenden muss.
Die Codekopie lautet wie folgt:
var dosomething = function (andere) {
var s = service ();
var r = Router ();
};
Um es interessanter zu machen, akzeptiert diese Funktion ein Argument. Natürlich können wir den obigen Code vollständig verwenden, aber dies ist offensichtlich nicht flexibel genug. Was ist, wenn wir ServiceXML oder ServiceJson verwenden möchten oder was, wenn wir einige Testmodule benötigen? Wir können das Problem nicht lösen, indem wir den Funktionskörper allein bearbeiten. Erstens können wir die Abhängigkeit durch die Parameter der Funktion lösen. Im Augenblick:
Die Codekopie lautet wie folgt:
var dosomething = function (Service, Router, andere) {
var s = service ();
var r = Router ();
};
Wir implementieren die gewünschten Funktionen, indem wir zusätzliche Parameter übergeben. Dies bringt jedoch neue Probleme mit sich. Stellen Sie sich vor, unsere Dosenmethode ist in unserem Code verteilt. Wenn wir die Abhängigkeitsbedingungen ändern müssen, ist es uns unmöglich, alle Dateien zu ändern, die die Funktion aufrufen.
Wir brauchen ein Tool, mit dem wir diese Dinge erledigen können. Dies ist das Problem, dass die Abhängigkeitsinjektion versucht zu lösen. Lassen Sie uns einige der Ziele aufschreiben, die unsere Abhängigkeitsinjektionslösung erreichen sollte:
Wir sollten in der Lage sein, Abhängigkeiten zu registrieren
1. Die Injektion sollte eine Funktion akzeptieren und eine Funktion zurückgeben, die wir benötigen
2. Wir können nicht zu viel schreiben - wir müssen die schöne Grammatik rationalisieren
3. Die Injektion sollte den Umfang der übertragenen Funktion aufrechterhalten
4. Die über bestehende Funktion sollte in der Lage sein, benutzerdefinierte Parameter zu akzeptieren, nicht nur von Beschreibungen abhängen
5. Eine perfekte Liste, lassen Sie sie unten sie unten erreichen.
3.. Erforderliche/AMD -Methode
Möglicherweise haben Sie von Anforderungen gehört, was eine gute Wahl ist, um die Abhängigkeitsinjektion zu lösen.
Die Codekopie lautet wie folgt:
Define (['Service', 'Router'], Funktion (Service, Router) {
// ...
});
Die Idee ist, zuerst die erforderlichen Abhängigkeiten zu beschreiben und dann Ihre Funktion zu schreiben. Die Reihenfolge der Parameter hier ist sehr wichtig. Wie oben erwähnt, schreiben wir ein Modul namens Injector, das dieselbe Syntax akzeptieren kann.
Die Codekopie lautet wie folgt:
var dosomething = injector.resolve (['Service', 'Router'], Funktion (Service, Router, andere) {
erwarten (service (). name) .to.be ('service');
erwarten (Router (). Name) .to.be ('Router');
erwarten (andere). to.be ('andere');
});
Dosen ("andere");
Bevor ich weitergeht, sollte ich den Inhalt der Dosenfunktion eindeutig erklären. Verfahren.
Beginnen wir unser Injektormodul, das ein großartiges Singleton -Muster ist. Daher funktioniert es in verschiedenen Teilen unseres Programms gut.
Die Codekopie lautet wie folgt:
var injector = {
Abhängigkeiten: {},
Register: Funktion (Schlüssel, Wert) {
Diese Abhängigkeiten [Schlüssel] = Wert;
},
Auflösung: Funktion (DEPS, Func, Scope) {
}
}
Dies ist ein sehr einfaches Objekt mit zwei Methoden zum Speichern der Eigenschaft. Wir möchten das DEPS -Array überprüfen und nach Antworten in der Abhängigkeitsvariablen suchen. Alles, was übrig bleibt, ist, die .Apply -Methode aufzurufen und die Parameter der vorherigen Func -Methode zu übergeben.
Die Codekopie lautet wie folgt:
Auflösung: Funktion (DEPS, Func, Scope) {
var args = [];
für (var i = 0; i <deps.length, d = deps [i]; i ++) {
if (this.Dependenzen [d]) {
args.push (this. -Abhängigkeiten [d]);
} anders {
Neuen Fehler werfen ('Can/' t lösen ' + d);
}
}
return function () {
func.apply (Scope || {}, args.concat (array.prototype.slice.call (Argumente, 0)));
}
}
Umfang ist optional, Array.Prototype.slice.call (Argumente, 0) ist erforderlich, um Argumentevariablen in reale Arrays umzuwandeln. Bisher ist es nicht schlecht. Unser Test verging. Das Problem bei dieser Implementierung ist, dass wir die erforderlichen Teile zweimal schreiben müssen und ihre Bestellung nicht verwirren können. Zusätzliche benutzerdefinierte Parameter stehen immer hinter der Abhängigkeit.
4. Reflexionsmethode
Nach der Definition von Wikipedia bezieht sich die Reflexion auf die Fähigkeit eines Programms, die Struktur und das Verhalten eines Objekts zur Laufzeit zu überprüfen und zu ändern. Einfach im Kontext von JavaScript bezieht sich dies ausdrücklich auf den Quellcode eines Objekts oder einer Funktion, die gelesen und analysiert wird. Lassen Sie uns die zu Beginn des Artikels erwähnte Dosenfunktion ausfüllen. Wenn Sie Dosen ausgeben.ToString () in der Konsole. Sie erhalten die folgende Zeichenfolge:
Die Codekopie lautet wie folgt:
"Funktion (Dienst, Router, andere) {
var s = service ();
var r = Router ();
} "
Die nach dieser Methode zurückgegebene Zeichenfolge gibt uns die Möglichkeit, Parameter zu durchqueren und vor allem ihre Namen zu erhalten. Dies ist tatsächlich die Methode von Angular zur Implementierung seiner Abhängigkeitsinjektion. Ich war ein wenig faul und fing den regulären Ausdruck direkt ab, der die Parameter im Winkelcode erhält.
Die Codekopie lautet wie folgt:
/^Funktion/s*[^/(]*/(/s*([^/)]*)/)/m
Wir können den Resolve -Code wie diesen ändern:
Die Codekopie lautet wie folgt:
Resolve: function () {
var func, dEPs, Umfang, args = [], self = this;
Func = Argumente [0];
DEPs = func.toString (). Übereinstimmung (/^function/s*[^/(]*/(/s*([^/)]*)/)/m) [1] .Replace (//g,, '').Teilt(',');
Scope = Argumente [1] || {};
return function () {
var a = array.prototype.slice.call (Argumente, 0);
für (var i = 0; i <deps.length; i ++) {
var d = dEPs [i];
args.push (self.dependenzen [d] && d! = ''? self.dependenzen [d]: a.shift ());
}
func.apply (Scope || {}, args);
}
}
Das Ergebnis unserer Ausführung regulärer Ausdrücke ist wie folgt:
Die Codekopie lautet wie folgt:
["Funktion (Service, Router, andere)", "Service, Router, andere"]
Es scheint, dass wir nur den zweiten Artikel brauchen. Sobald wir die Räume löschen und die Saite teilen, erhalten wir das DEPS -Array. Es gibt nur eine große Veränderung:
Die Codekopie lautet wie folgt:
var a = array.prototype.slice.call (Argumente, 0);
...
args.push (self.dependenzen [d] && d! = ''? self.dependenzen [d]: a.shift ());
Wir durchlaufen das Abhängigkeitsarray und versuchen, es aus dem Argumenteobjekt zu erhalten, wenn wir fehlende Elemente finden. Wenn das Array leer ist, gibt die Umschaltmethode dankenswerterweise undefiniert, anstatt einen Fehler zu werfen (dies ist der Idee des Webs zu verdanken). Die neue Version des Injektors kann wie folgt verwendet werden:
Die Codekopie lautet wie folgt:
var dosomething = injector.resolve (Funktion (Service, andere, Router) {
erwarten (service (). name) .to.be ('service');
erwarten (Router (). Name) .to.be ('Router');
erwarten (andere). to.be ('andere');
});
Dosen ("andere");
Es besteht keine Notwendigkeit, Abhängigkeiten neu zu schreiben, und ihre Bestellung kann gestört werden. Es funktioniert immer noch und wir kopierten Angulars Magie erfolgreich.
Diese Praxis ist jedoch nicht perfekt, was ein sehr großes Problem bei der Reflex -Injektion ist. Komprimierung wird unsere Logik zerstören, da sie den Namen des Parameters ändert und wir nicht in der Lage sein werden, die korrekte Zuordnungsbeziehung aufrechtzuerhalten. Zum Beispiel könnte DoSometing () nach der Komprimierung so aussehen:
Die Codekopie lautet wie folgt:
var dosomething = function (e, t, n) {var r = e (); var i = t ()}
Die vom Angular -Team vorgeschlagene Lösung sieht aus wie:
var dosomething = injector.resolve (['Service', 'Router', Funktion (Service, Router) {
}]);
Das sieht der Lösung, mit der wir begonnen haben, sehr ähnlich. Ich konnte keine bessere Lösung finden und beschloss, beide zu kombinieren. Hier ist die endgültige Version von Injector.
Die Codekopie lautet wie folgt:
var injector = {
Abhängigkeiten: {},
Register: Funktion (Schlüssel, Wert) {
Diese Abhängigkeiten [Schlüssel] = Wert;
},
Resolve: function () {
var func, dEPs, Umfang, args = [], self = this;
if (Typeof Argumente [0] === 'String') {
Func = Argumente [1];
DEPs = Argumente [0] .Replace ( / / g, '') .Split (',');
Scope = Argumente [2] || {};
} anders {
Func = Argumente [0];
DEPs = func.toString (). Übereinstimmung (/^function/s*[^/(]*/(/s*([^/)]*)/)/m) [1] .Replace (//g,, '').Teilt(',');
Scope = Argumente [1] || {};
}
return function () {
var a = array.prototype.slice.call (Argumente, 0);
für (var i = 0; i <deps.length; i ++) {
var d = dEPs [i];
args.push (self.dependenzen [d] && d! = ''? self.dependenzen [d]: a.shift ());
}
func.apply (Scope || {}, args);
}
}
}
Auflösende Besucher akzeptieren zwei oder drei Parameter, wenn es zwei Parameter gibt, ist es tatsächlich dasselbe wie das, was im vorherigen Artikel geschrieben wurde. Wenn es jedoch drei Parameter gibt, wandelt es den ersten Parameter um und füllt das DEPS -Array. Hier ist ein Testbeispiel:
Die Codekopie lautet wie folgt:
var dosomething = injector.resolve ('Router ,, Service', Funktion (a, b, c) {
erwarten (a (). name) .to.be ('Router');
erwarten (b). to.be ('andere');
erwarten (c (). name) .to.be ('service');
});
Dosen ("andere");
Sie können feststellen, dass es nach dem ersten Parameter zwei Kommas gibt - beachten Sie, dass dies kein Tippfehler ist. Ein Nullwert repräsentiert tatsächlich den "anderen" Parameter (Platzhalter). Dies zeigt, wie wir die Reihenfolge der Parameter steuern.
5. Direktinjektion des Umfangs
Manchmal verwende ich die dritte Injektionsvariable, die den Umfang der Operationsfunktion beinhaltet (mit anderen Worten dieses Objekt). Daher ist diese Variable in vielen Fällen nicht erforderlich.
Die Codekopie lautet wie folgt:
var injector = {
Abhängigkeiten: {},
Register: Funktion (Schlüssel, Wert) {
Diese Abhängigkeiten [Schlüssel] = Wert;
},
Auflösung: Funktion (DEPS, Func, Scope) {
var args = [];
Scope = Scope || {};
für (var i = 0; i <deps.length, d = deps [i]; i ++) {
if (this.Dependenzen [d]) {
SCOPE [D] = this.abendenzen [d];
} anders {
Neuen Fehler werfen ('Can/' t lösen ' + d);
}
}
return function () {
func.apply (Scope || {}, Array.Prototype.slice.call (Argumente, 0));
}
}
}
Wir tun nur Abhängigkeiten zum Geltungsbereich. Der Vorteil davon ist, dass Entwickler keine Abhängigkeitsparameter mehr schreiben müssen.
Die Codekopie lautet wie folgt:
var dosomething = injector.resolve (['service', 'router'], function (other) {
erwarten (this.service (). name) .to.be ('service');
erwarten (this.router (). name) .to.be ('Router');
erwarten (andere). to.be ('andere');
});
Dosen ("andere");
6. Schlussfolgerung
Tatsächlich haben die meisten von uns Abhängigkeitsinjektion verwendet, aber wir erkennen es nicht. Selbst wenn Sie den Begriff nicht kennen, haben Sie ihn möglicherweise millionenfach in Ihrem Code verwendet. Ich hoffe, dieser Artikel wird Ihr Verständnis davon vertiefen.