Javascriptの中の依存注入を深く理解します。


遅かれ早かれ他の開発者の抽象的な成果が必要です。つまり、他人のコードに依存します。私は自由に依存するモジュールが好きですが、それは難しいです。あなたが作ったきれいな黒い箱のセットも多かれ少なかれいくつかのものに依存します。これこそが、注入による活躍の場です。今は依存する能力を効果的に管理する必要があります。この論文は問題探索といくつかの解決策をまとめた。
一、目標は二つのモジュールがあります。一つ目はAjaxリクエストサービス、二つ目はルートです。

var service = function() {
    return { name: 'Service' };
}
var router = function() {
    return { name: 'Router' };
}
他の関数があります。この二つのモジュールを使用する必要があります。

var doSomething = function(other) {
    var s = service();
    var r = router();
};
は、より面白く見えるように、この関数はパラメータを受け入れる。もちろん、上のコードは全部使えますが、これは明らかに融通がきかないです。もし私たちがServiceXMLやServiceJSONを使いたいなら、あるいはテストモジュールが必要です。私たちは関数を編集するだけで問題を解決できない。まず,依存性を関数のパラメータで解決できた。すなわち、

var doSomething = function(service, router, other) {
    var s = service();
    var r = router();
};
は、私たちが望む機能を追加のパラメータを伝えることによって実現しますが、これは新しい問題をもたらします。もし私たちのdoSomething方法がコードに散在したらと想像します。依存条件を変更する必要があるなら、すべての呼び出し関数のファイルを変更することはできません。
これらを解決してくれるツールが必要です。これは注入に依存して解決しようとする問題である。いくつかの私達の依存注入解決法が達成すべき目標を書きましょう。
私たちは依存関係を登録することができます。1つの関数を受け入れて、私たちが必要とする関数を返すべきです。私たちは多すぎるものを書くことができません。きれいな文法が必要です。3.伝達関数を維持すべき作用領域を注入します。4.伝達される関数は、説明に依存するだけでなく、カスタムパラメータを受け入れることができます。完璧なリストと言えます。これを実現しましょう。三、RequireJS/AMDの方法はRequireJSに対してもっと前に耳にしたことがあるかもしれません。それは依存注入のいい選択を解決することです。

define(['service', 'router'], function(service, router) {      
    // ...
});
この考えは必要な依存性を先に説明してから関数を書きます。ここではパラメータの順序が重要です。上記のように、injectorというモジュールを書いて、同じ文法を受けられます。

var doSomething = injector.resolve(['service', 'router'], function(service, router, other) {
    expect(service().name).to.be('Service');
    expect(router().name).to.be('Router');
    expect(other).to.be('Other');
});
doSomething(「Other」)引き続き前にdoSomething関数の内容を説明します。expect.jsを使って、私が書いたコードの行動を保証するためだけに、私が望むのと同じです。少しずつTDD(テストドライブ開発)の方法を表します。次に私たちのinjectorモジュールを開始します。これはとても素晴らしい一例モデルです。だから、私たちのプログラムの違う部分でよく働きます。

var injector = {
    dependencies: {},
    register: function(key, value) {
        this.dependencies[key] = value;
    },
    resolve: function(deps, func, scope) {

    }
}

これは非常に単純なオブジェクトであり、2つの方法があり、1つは記憶のための属性である。私たちがやるべきことはdeps配列を調べてdependencies変数の中で答えを検索することです。残りは.appyメソッドを呼び出して,前のfuncメソッドのパラメータを伝えるだけです。

resolve: function(deps, func, scope) {
    var args = [];
    for(var i=0; i<deps.length, d=deps[i]; i++) {
        if(this.dependencies[d]) {
            args.push(this.dependencies[d]);
        } else {
            throw new Error('Can\'t resolve ' + d);
        }
    }
    return function() {
        func.apply(scope || {}, args.concat(Array.prototype.slice.call(arguments, 0)));
    }       
}
scopeはオプションで、argments変数を本物の配列に変換するために必要です。今まではいいです。私たちのテストは合格しました。この実装の問題は、必要な部品を2回書く必要があり、彼らの順番を混同することができません。追加のカスタムパラメータは常に依存関係の後にあります。
四、反射方法はウィキペディアの定義による反射とは、プログラムの実行時にオブジェクトの構造と行為を検査し、修正する能力をいう。簡単に言えば、JavaScriptのコンテキストでは、これは具体的に読み取りと分析の対象または関数のソースコードを指します。文章の冒頭で述べたdoSomething関数を完成しましょう。コンソールで出力すればdoSomething.tonttringです。この方法で

"function (service, router, other) {
    var s = service();
    var r = router();
}"
に戻った文字列はパラメータを遍歴する能力を与えます。もっと重要なのは彼らの名前を取得することです。これは実はアングラーがその依存注入を実現する方法です。怠惰な点を盗んで、直接Anglarコードからパラメータを取得する正規表現を切り取りました。

/^function\s*[^\(]*\(\s*([^\)]*)\)/m
私たちは、次のようにresoveのコードを修正できます。

resolve: function() {
    var func, deps, scope, args = [], self = this;
    func = arguments[0];
    deps = func.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].replace(/ /g, '').split(',');
    scope = arguments[1] || {};
    return function() {
        var a = Array.prototype.slice.call(arguments, 0);
        for(var i=0; i<deps.length; i++) {
            var d = deps[i];
            args.push(self.dependencies[d] && d != '' ? self.dependencies[d] : a.shift());
        }
        func.apply(scope || {}, args);
    }       
}
私たちは正規表現を実行します。結果は以下の通りです。

["function (service, router, other)", "service, router, other"]
は第二項だけが必要です。スペースを明確にして文字列を分割すると、deps配列が得られます。大きな変化だけがあります。

var a = Array.prototype.slice.call(arguments, 0);
...
args.push(self.dependencies[d] && d != '' ? self.dependencies[d] : a.shift());
はdependencies配列を巡回します。もし欠落項目が見つかったら、argmentsオブジェクトから取得してみます。ありがとうございます。配列が空の時に、シフト方法はundefinedに戻るだけで、間違ったことを投げたのではありません。新版のinjectorは次のように使うことができます。

var doSomething = injector.resolve(function(service, other, router) {
    expect(service().name).to.be('Service');
    expect(router().name).to.be('Router');
    expect(other).to.be('Other');
});
doSomething("Other");
は書き換える必要がなくて、彼らの順序が乱れることができます。まだ有効です。Anglarの魔法を成功裏にコピーしました。
しかし、このようなやり方は完璧ではありません。これは反射型注射の非常に大きな問題です。圧縮は私たちの論理を破壊します。パラメータの名前を変えるので、正しいマッピング関係を維持できません。例えば、ドソmeting()圧縮後には、

var doSomething=function(e,t,n){var r=e();var i=t()}
Angular :

var doSomething = injector.resolve(['service', 'router', function(service, router) {

}]);

というように見えるかもしれません。これは私たちの開始時の解決策と似ています。私はより良い解決策を見つけられませんでしたので、この二つの方法を結び付けることにしました。以下はinjectorの最終バージョンです。

var injector = {
    dependencies: {},
    register: function(key, value) {
        this.dependencies[key] = value;
    },
    resolve: function() {
        var func, deps, scope, args = [], self = this;
        if(typeof arguments[0] === 'string') {
            func = arguments[1];
            deps = arguments[0].replace(/ /g, '').split(',');
            scope = arguments[2] || {};
        } else {
            func = arguments[0];
            deps = func.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].replace(/ /g, '').split(',');
            scope = arguments[1] || {};
        }
        return function() {
            var a = Array.prototype.slice.call(arguments, 0);
            for(var i=0; i<deps.length; i++) {
                var d = deps[i];
                args.push(self.dependencies[d] && d != '' ? self.dependencies[d] : a.shift());
            }
            func.apply(scope || {}, args);
        }       
    }
}
レシオ訪問者は2つまたは3つのパラメータを受け取ります。もし2つのパラメータがあれば、それは実際に文章の前に書いたのと同じです。しかし、3つのパラメータがあると、最初のパラメータをdeps配列に変換して充填します。以下はテスト例です。

var doSomething = injector.resolve('router,,service', function(a, b, c) {
    expect(a().name).to.be('Router');
    expect(b).to.be('Other');
    expect(c().name).to.be('Service');
});
doSomething("Other");
は、最初のパラメータの後ろに2つのカンマがあることに気づくかもしれません。空の値は、実際には「Other」パラメータ(プレースホルダ)を表します。これはパラメータ順序をどのように制御するかを示している。
五、直接Scopeを注入する場合、第三の注入変数を使用します。操作関数の作用領域(言い換えれば、thisオブジェクト)に関連します。したがって、この変数を使う必要がない場合が多いです。

var injector = {
    dependencies: {},
    register: function(key, value) {
        this.dependencies[key] = value;
    },
    resolve: function(deps, func, scope) {
        var args = [];
        scope = scope || {};
        for(var i=0; i<deps.length, d=deps[i]; i++) {
            if(this.dependencies[d]) {
                scope[d] = this.dependencies[d];
            } else {
                throw new Error('Can\'t resolve ' + d);
            }
        }
        return function() {
            func.apply(scope || {}, Array.prototype.slice.call(arguments, 0));
        }       
    }
}
私たちがしているすべては、実際には依存をスコープに追加することです。このようにするメリットは、開発者が依存性パラメータを書く必要がないことです。それらは既に関数のスコープの一部です。

var doSomething = injector.resolve(['service', 'router'], function(other) {
    expect(this.service().name).to.be('Service');
    expect(this.router().name).to.be('Router');
    expect(other).to.be('Other');
});
doSomething("Other");
六、結語は実は私達の大部分の人はすべて注入に依存したことがあって、ただ私達は意識していません。この用語を知らなくても、コードの中でそれを百万回使ってもいいです。この文章があなたの理解を深めてくれることを願っています。