コンピューター プログラミングの世界は、実際には、単純な部分を常に抽象化し、これらの抽象概念を整理するプロセスです。 JavaScript も例外ではありません。私たちが JavaScript を使用してアプリケーションを作成するとき、有名なオープン ソース ライブラリやフレームワークなど、他の人が作成したコードを使用するのでしょうか。プロジェクトが成長するにつれて、依存する必要のあるモジュールがますます増えており、現時点では、これらのモジュールを効果的に編成する方法が非常に重要な問題となっています。依存関係の注入は、コードに依存するモジュールを効果的に編成する方法の問題を解決します。有名なフロントエンド フレームワーク AngularJS など、一部のフレームワークやライブラリで「依存性注入」という用語を聞いたことがあるかもしれません。依存性注入は非常に重要な機能の 1 つです。ただし、依存性注入はまったく新しいものではありません。PHP などの他のプログラミング言語には以前から存在していました。同時に、依存関係の注入は想像されているほど複雑ではありません。この記事では、JavaScript における依存性注入の概念を学び、「依存性注入スタイル」のコードの書き方をわかりやすく説明します。
目標設定
今、2 つのモジュールがあると仮定します。最初のモジュールは Ajax リクエストの送信に使用され、2 番目のモジュールはルーターとして使用されます。
var service = function() {
Return { name: 'サービス' };
}
var router = function() {
Return { 名前: 'ルーター' };
}
現時点では、上記の 2 つのモジュールの使用を必要とする関数を作成しました:
var doSomething = function(other) {
var s = サービス();
var r = router();
};
ここで、コードをより興味深いものにするために、このパラメータはさらにいくつかのパラメータを受け取る必要があります。もちろん、上記のコードを完全に使用することはできますが、上記のコードはどの面から見ても柔軟性が若干劣ります。使用する必要があるモジュールの名前が ServiceXML または ServiceJSON に変更された場合はどうなりますか?あるいは、テスト目的で偽のモジュールを使用したい場合はどうなるでしょうか。この時点では、関数自体を編集するだけでは済みません。したがって、最初に行う必要があるのは、依存モジュールをパラメーターとして関数に渡すことです。コードは次のとおりです:
var doSomething = function(サービス、ルーター、その他) {
var s = サービス();
var r = router();
};
上記のコードでは、必要なモジュールを正確に渡します。しかし、これは新たな問題を引き起こします。コードの両方の部分で doSomething メソッドを呼び出すとします。この時点で、3 番目の依存関係が必要な場合はどうなるでしょうか。現時点では、すべての関数呼び出しコードを編集するのは賢明な考えではありません。したがって、これを行うにはコードが必要です。これは、依存性インジェクターが解決しようとしている問題です。これで目標を設定できます:
1. 依存関係を登録できるはずです
2. 依存関係インジェクターは関数を受け取り、必要なリソースを取得できる関数を返す必要があります
3. コードは複雑であってはなりませんが、シンプルで親しみやすいものである必要があります
4. 依存関係インジェクターは、渡された関数スコープを維持する必要があります
5. 渡された関数は、記述された依存関係だけでなく、カスタム パラメーターを受け取ることができる必要があります
JS/AMD メソッドが必要
おそらく、有名な requirejs について聞いたことがあるでしょう。これは、依存関係注入の問題を非常にうまく解決できるライブラリです。
define(['サービス', 'ルータ'], function(サービス, ルータ) {
// ...
});
requirejs の考え方は、最初に必要なモジュールを記述してから、独自の関数を記述する必要があるということです。中でもパラメータの順序は重要です。同様の構文を実装できる injector というモジュールを作成する必要があるとします。
var doSomething = injector.resolve(['service', 'router'], function(service, router, other) {
Expect(service().name).to.be('Service');
Expect(router().name).to.be('ルーター');
Expect(other).to.be('Other');
});
doSomething("その他");
先に進む前に、doSomething の関数本体で、expect.js アサーション ライブラリを使用してコードの正確さを確認する必要があることを 1 つ説明する必要があります。ここにはTDD(テスト駆動開発)の考え方に似たものがあります。
ここで、インジェクター モジュールの作成を正式に開始します。まず、アプリケーションのすべての部分で同じ機能を持たせるために、モノリスである必要があります。
var インジェクター = {
依存関係: {}、
レジスタ: function(key, value) {
This.dependency[key] = 値;
}、
解決: function(deps, func,scope) {
}
}
このオブジェクトは非常に単純で、2 つの関数と保存用の変数のみが含まれています。私たちがしなければならないことは、deps 配列をチェックし、依存関係変数で答えを探すことです。残りの部分は、.apply メソッドを使用して、渡した func 変数を呼び出すことです:
解決: function(deps, func,scope) {
var args = [];
for(var i=0; i
if(this.dependency[d]) {
args.push(this.dependency[d]);
} else {
throw new Error('Can'tsolve ' d);
}
}
戻り関数() {
func.apply(scope || {}, args.concat(Array.prototype.slice.call(arguments, 0)));
}
スコープを指定する必要がある場合は、上記のコードも通常どおり実行できます。
上記のコードでは、Array.prototype.slice.call(arguments, 0) の機能は、引数変数を実数の配列に変換することです。これまでのところ、私たちのコードはテストに完全に合格しています。しかし、ここでの問題は、必要なモジュールを 2 回書かなければならず、それらを任意の順序で配置できないことです。追加のパラメーターは常にすべての依存関係の後に来ます。
反映方法
Wikipedia の説明によると、リフレクションとは、プログラムの実行中にオブジェクトが自身の構造や動作を変更できることを意味します。 JavaScriptでは、簡単に言えば、オブジェクトのソースコードを読み取って、ソースコードを解析する機能です。 doSomething メソッドに戻りますが、doSomething.toString() メソッドを呼び出すと、次の文字列を取得できます:
"関数 (サービス、ルーター、その他) {
var s = サービス();
var r = router();
}"
このように、このメソッドを使用する限り、必要なパラメータ、そしてさらに重要なことに、その名前を簡単に取得できます。これは、AngularJS が依存関係の注入を実装するために使用するメソッドでもあります。 AngularJS コードでは、次の正規表現が確認できます:
/^関数*[^(]*(s*([^)]*))/m
解決メソッドを以下に示すコードに変更できます:
解決: function() {
var func、deps、scope、args = []、self = this;
func = 引数[0];
deps = func.toString().match(/^functions*[^(]*(s*([^)]*))/m)[1].replace(/ /g, '').split(' 、');
スコープ = 引数[1] {};
戻り関数() {
var a = Array.prototype.slice.call(arguments, 0);
for(var i=0; i
var d = deps[i];
args.push(self.dependency[d] && d != '' ? self.dependency[d] : a.shift());
}
func.apply(scope || {}, args);
}
上記の正規表現を使用して定義した関数と一致させると、次の結果が得られます:
["機能(サービス、ルーター、その他)"、"サービス、ルーター、その他"]
この時点で必要なのは 2 番目の項目だけです。ただし、余分なスペースを削除し、文字列を で分割すると、deps 配列が得られます。次のコードは、変更した部分です:
var a = Array.prototype.slice.call(arguments, 0);
...
args.push(self.dependency[d] && d != '' ? self.dependency[d] : a.shift());
上記のコードでは、依存プロジェクトを走査し、依存プロジェクトに欠落している部分がある場合は、それらを引数オブジェクトから取得します。配列が空の場合、shift メソッドを使用すると、エラーはスローされずに単純に unknown が返されます。これまでのところ、インジェクターの新しいバージョンは次のようになります:
var doSomething = injector.resolve(function(service, other, router) {
Expect(service().name).to.be('Service');
Expect(router().name).to.be('ルーター');
Expect(other).to.be('Other');
});
doSomething("その他");
上記のコードでは、依存関係の順序を自由に混ぜることができます。
しかし、完璧なものはありません。リフレクティブ メソッドの依存性注入には非常に深刻な問題があります。コードを単純化するとエラーが発生します。これは、コードの簡略化プロセス中にパラメーターの名前が変更され、依存関係の解決に失敗する可能性があるためです。例:
var doSomething=function(e,t,n){var r=e();var i=t()}
したがって、AngularJS と同様に、次の解決策が必要です:
var doSomething = injector.resolve(['service', 'router', function(service, router) {
}]);
これは最初に見た AMD ソリューションに非常に似ているため、上記の 2 つの方法を統合できます。最終的なコードは次のとおりです。
コードをコピー コードは次のとおりです:
var インジェクター = {
依存関係: {}、
register: function(key, value) {
this.dependency[key] = 値;
}、
解決: function() {
var func、deps、scope、args = []、self = this;
if(引数の種類[0] === '文字列') {
func = 引数[1];
deps = argument[0].replace(/ /g, '').split(',');
スコープ = 引数[2] || {};
} else {
func = 引数[0];
deps = func.toString().match(/^functions*[^(]*(s*([^)]*))/m)[1].replace(/ /g, '').split(' 、');
スコープ = 引数[1] || {};
}
return function() {
var a = Array.prototype.slice.call(arguments, 0);
for(var i=0; i
var d = deps[i];
args.push(self.dependency[d] && d != '' ? self.dependency[d] : a.shift());
}
func.apply(scope || {}, args);
}
}
}
このバージョンの解決メソッドは 2 つまたは 3 つのパラメータを受け取ることができます。
var doSomething = injector.resolve('router,,service', function(a, b, c) {
Expect(a().name).to.be('ルーター');
Expect(b).to.be('その他');
Expect(c().name).to.be('サービス');
});
doSomething("その他");
2 つの番号の間に何も存在しないことに注意してください。これは危険な話ではありません。このスペースは他のパラメータに保持されています。これがパラメータの順序を制御する方法です。
结语
上の内容では、JavaScript での依存注入の方法を紹介しており、この技術の使用を開始し、依存注入のコードを書き出すのに役立つことを期待しています。