web-dev-qa-db-ja.com

JavaScript依存性注入

JavaScriptが初めてです。 JavaScriptで依存性注入がどのように実装されているのだろうか?インターネットで検索しましたが、何も見つかりませんでした。

34
Levent Sezer
var Injector = {
   dependencies: {},
   add : function(qualifier, obj){
      this.dependencies[qualifier] = obj; 
   },
   get : function(func){
      var obj = new func;
      var dependencies = this.resolveDependencies(func);
      func.apply(obj, dependencies);
      return obj;
   },
   resolveDependencies : function(func) {
      var args = this.getArguments(func);
      var dependencies = [];
      for ( var i = 0; i < args.length; i++) {
         dependencies.Push(this.dependencies[args[i]]);
      }
      return dependencies;
   },
   getArguments : function(func) {
      //This regex is from require.js
      var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
      var args = func.toString().match(FN_ARGS)[1].split(',');
      return args;
   }
};

最初に必要な依存関係を修飾子で提供するための構成が必要です。そのために、Injectorクラスの依存関係として依存関係セットを定義します。コンテナに依存関係セットを使用して、修飾子にマッピングされたオブジェクトインスタンスを処理します。修飾子付きの新しいインスタンスを依存関係セットに追加するには、addメソッドを定義します。その後、getメソッドを定義してインスタンスを取得します。このメソッドでは、最初にarguments配列を見つけてから、それらの引数を依存関係にマップします。その後、依存関係を持つオブジェクトを作成し、それを返します。詳細と例については、私のブログの post を参照してください。

38
yusufaytas

AngularJSを例として使用できます。それが良いことかどうかは、自分で決めなければなりません。私は1週間前に AngularJSでの依存性注入の解消に関する記事 を書きました。ここで、記事のコードを読むことができます:

// The following simplified code is partly taken from the AngularJS source code:
// https://github.com/angular/angular.js/blob/master/src/auto/injector.js#L63

function inject(fn, variablesToInject) {
    var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
    var FN_ARG_SPLIT = /,/;
    var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
    var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;

    if (typeof fn === 'function' && fn.length) {
        var fnText = fn.toString(); // getting the source code of the function
        fnText = fnText.replace(STRIP_COMMENTS, ''); // stripping comments like function(/*string*/ a) {}

        var matches = fnText.match(FN_ARGS); // finding arguments
        var argNames = matches[1].split(FN_ARG_SPLIT); // finding each argument name

        var newArgs = [];
        for (var i = 0, l = argNames.length; i < l; i++) {
            var argName = argNames[i].trim();

            if (!variablesToInject.hasOwnProperty(argName)) {
                // the argument cannot be injected
                throw new Error("Unknown argument: '" + argName + "'. This cannot be injected.");
            }

            newArgs.Push(variablesToInject[argName]);
        }

        fn.apply(window, newArgs);
    }
}

function sum(x, y) {
    console.log(x + y);
}

inject(sum, {
    x: 5,
    y: 6
}); // should print 11

inject(sum, {
    x: 13,
    y: 45
}); // should print 58

inject(sum, {
    x: 33,
    z: 1 // we are missing 'y'
}); // should throw an error: Unknown argument: 'y'. This cannot be injected.
9
bdadam

私にとって yusufaytas 答えはまさに私が必要としていたものでした!不足している機能は次のとおりです。

  1. カスタムパラメータを使用して依存関係を取得します。
  2. コールバックを使用して依存関係を登録します。

私はこのようなことをする能力を持ちたかったのです。

Injector.register('someDependency', function () {
        return new ConcreteDependency();
});

function SomeViewModel(userId, someDependency) {
    this.userId = userId;
    this.someDependency = someDependency;
}

var myVm = Injector.get(SomeViewModel, { "userId": "1234" });

だから私は次のコードになりました:

var Injector = {

    factories = {},        
    singletons = {},

    register: function (key, factory) {
        this.factories[key] = factory;
    },

    registerSingle: function (key, instance) {
        this.singletons[key] = instance;
    },

    get: function (CTor, params) {            

        var dependencies = this.resolveDependencies(CTor, params);

        // a workaround to allow calling a constructor through .apply
        // see https://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible
        function MiddlemanCTor() {
            CTor.apply(this, dependencies);
        }

        MiddlemanCTor.prototype = CTor.prototype;

        return new MiddlemanCTor();
    },

    resolveDependencies: function(CTor, params) {
        params = params || {};
        var args = this.getArguments(CTor);

        var dependencies = [];
        for (var i = 0; i < args.length; i++) {
            var paramName = args[i];
            var factory = this.factories[paramName];

            // resolve dependency using:
            // 1. parameters supplied by caller
            // 2. registered factories
            // 3. registered singletons
            var dependency = params[paramName] ||
                (typeof factory === "function" ? factory() : undefined) ||
                this.singletons[paramName];

            dependencies.Push(dependency);
        }
        return dependencies;
    }

    getArguments: func(func) {
        // Regex from require.js
        var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
        var args = func.toString().match(FN_ARGS)[1].split(',').map(function (str) {
            return str.trim();
        });
        return args;
    }
};

アップデート-21.5.2018

私は数年前からこのソリューションを使用しています。コードベースをTypeScriptに移行すると、TypeScriptとJavaScriptの両方をサポートするようにソリューションが進化しました。しばらくして、コードが運用環境で実行されていたので、最近(2日前)このソリューションに基づいてライブラリを公開しました。気軽にチェックして、未解決の問題などを見つけてください。

peppermint-di

6
Alon Bar

超シンプルな実世界の例を使って学びましょう:)

ここで説明するクラスの例はPrinterで、何かを出力するにはdriverが必要です。最終的に最良のソリューションに到達するために、4つのステップで依存性注入設計パターンの利点を実証しました。

ケース1:依存性注入は使用されていません:

class Printer {
   constructor () {
      this.lcd = '';
   }

   /* umm! Not so flexible! */
   print (text) {
     this.lcd = 'printing...';
     console.log (`This printer prints ${text}!`);
   }
}

// Usage:

var printer = new Printer ();
printer.print ('hello');

使い方は簡単で、この方法で新しいプリンターを作成するのは簡単ですが、このプリンターは柔軟性がありません。

Case2:printメソッド内の機能をDriverという新しいクラスに抽象化します:

class Printer {
  constructor () {
    this.lcd = '';
    this.driver = new Driver ();
  }

  print (text) {
    this.lcd = 'printing...';
    this.driver.driverPrint (text);
  }
}

class Driver {
  driverPrint (text) {
    console.log (`I will print the ${text}`);
  }
}

// Usage:

var printer = new Printer ();
printer.print ('hello');

そのため、Printerクラスは、さらにmodularcleanおよびeasy解き放ちますが、まだ柔軟ではありませんnewキーワードを使用すると、実際にはハードコーディング何かになります。この場合、constructingプリンター内のドライバーです。これは、実際には、決してできないドライバーが組み込まれたプリンターの例です変化する!

Case3:作成済みのドライバーをプリンターに挿入します

より良いバージョンは、プリンタを構築するときにドライバを注入することです。つまり、このタイプのドライバは、Printerクラスの外部で分離して作成され、与えられます(INJECTED!)。 Printer...に.

class Printer {
  constructor (driver) {
    this.lcd = '';
    this.driver = driver;
  }

  print (text) {
    this.lcd = 'printing...';
    this.driver.driverPrint (text);
  }
}

class BWDriver {
  driverPrint (text) {
    console.log (`I will print the ${text} in Black and White.`);
  }
}

class ColorDriver {
  driverPrint (text) {
    console.log (`I will print the ${text} in color.`);
  }
}

// Usage:
var bwDriver = new BWDriver ();
var printer = new Printer (bwDriver);
printer.print ('hello'); // I will print the hello in Black and White.

ユーザーとしてのプリンターの使用方法は異なります。最初にドライバーを作成(作成)してから、このドライバーをプリンターに渡す必要があります。エンドユーザーはシステムについてもう少し知る必要があるように思えるかもしれませんが、この構造はより柔軟性を与えます。ユーザーは、有効なドライバーを渡すことができます!たとえば、BWDriver(白黒)タイプのドライバーがあるとします。ユーザーはこのタイプの新しいドライバーを作成し、それを使用して白黒で印刷する新しいプリンターを作成できます。

ここまでは順調ですね!しかし、あなたが私たちがより良くできると思うこと、そしてあなたが思うことにはまだここで対処する余地がありますか?!あなたもそれを見ることができると確信しています!

新しいプリンターを作成しています毎回プリンターを異なるドライバーで印刷する必要があります!これは、構築時に選択したドライバーをPrinterクラスに渡すためです。ユーザーが別のドライバーを使用する場合、そのドライバーで新しいプリンターを作成する必要があります;たとえば、今私がする必要があるカラー印刷をしたい場合:

var cDriver = new ColorDriver ();
var printer = new Printer (cDriver); // Yes! This line here is the problem!
printer.print ('hello'); // I will print the hello in color.

ケース4:セッター機能を提供して、いつでもプリンターのドライバーを設定してください!

class Printer {
  constructor () {
    this.lcd = '';
  }

  setDriver (driver) {
    this.driver = driver;
  }

  print (text) {
    this.lcd = 'printing...';
    this.driver.driverPrint (text);
  }
}

class BWDriver {
  driverPrint (text) {
    console.log (`I will print the ${text} in Black and White.`);
  }
}

class ColorDriver {
  driverPrint (text) {
    console.log (`I will print the ${text} in color.`);
  }
}

// Usage:
var bwDriver = new BWDriver ();
var cDriver = new ColorDriver ();
var printer = new Printer (); // I am happy to see this line only ONCE!

printer.setDriver (bwDriver);
printer.print ('hello'); // I will print the hello in Black and White.

printer.setDriver (cDriver);
printer.print ('hello'); // I will print the hello in color.

依存性注入は理解するのが本当に難しい概念ではありません。この用語は少し過負荷になっているかもしれませんが、その目的を理解すると、ほとんどの場合それを使用していることに気付くでしょう。

6
Vennesa

DIはJS/ES2015のすぐに使える機能だと思います。 :-)もちろん、フル機能ではありませんIOC containersですが、便利そうですね?下の例をチェックしてください!

const one = () => 1;
const two = ({one}) => one + one;
const three = ({one, two}) => one + two;

// IOC container
const decimalNumbers = {
  get one() { return one(this); },
  get two() { return two(this); },
  get three() { return three(this); }
};

const binaryTwo = ({one}) => one + 9;

// child IOC container
const binaryNumbers = Object.create(decimalNumbers, {
  two: { get() { return binaryTwo(this); } }
});

console.log(`${decimalNumbers.three} is ${binaryNumbers.three} in binary`);

依存関係を_.onceアンダースコア または lodash を参照)でラップして、シングルトンに変換できます。

const Rand = function() {
  return (min, max) => min + Math.random() * (max - min) | 0;
};

const pair = function({Rand} = this) {
  return [Rand(10, 100), Rand(100, 1000)];
};

// IOC container
const ioc = Object.create({}, {
  Rand: {get: Rand},
  pair: {get: _.once(pair)} // singleton
});

console.log(`${[ioc.pair, ioc.pair === ioc.pair]}`);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
2
dizel3d

Di-Ninjaという独自のJavaScript Dependency Injection Frameworkをコーディングしました https://github.com/di-ninja/di-ninja

完全な機能を備えており、現在、私が知っているように、コンポジション-ルート設計パターンを実装するjavascriptで唯一のものであり、すべてのものを分離し、アプリケーションコンポーネントと設定を1つの一意のルート場所に配線するのに役立ちます。 http://blog.ploeh.dk/2011/07/28/CompositionRoot/

NodeJSとWebpackでうまく機能します

フィードバックをいただければ幸いです

1
Jo-Go

Flyspeckで略奪品を取ります: https://Gist.github.com/elfet/11349215

var c = new Flyspeck();

c.set('name', 'GistHub');

c.set('config', {
    server: 'https://Gist.github.com'
});

c.set('user', function (c) {
    return new User(c.get('name'));
});

c.extend('user', function (user, c) {
    return new ProxyUser(user);
});

c.set('app', function (c) {
    return new Application(c.get('config'), c.get('user'));
});

var app = c.get('app');
1
Medvedev

candiJSは、軽量の暗黙的な依存性注入およびオブジェクト作成ライブラリです。 ご覧ください

例:

candi.provider.singleton('ajax', function() {
    return {
        get: function() { /* some code */ },
        put: function() { /* some code */ }
    };
});

candi.provider.singleton('carService', function(ajax) {
    return {
        getSpecs: function(manufacturer, year, model, trim) {
            return ajax.get();
        }
    };
});

var Car = candi.provider.instance('Car', function(carService, year, manufacturer, model, trim) {
    this.year = year;
    this.manufacturer = manufacturer;
    this.model = model;
    this.trim = trim;
    this.specs = carService.getSpecs(manufacturer, year, model, trim);
});

var car = new Car(2009, 'honda', 'accord', 'lx');
0
bflemi3

これは古い質問ですが、衝動を感じます。 ;)

//dependency injection
class Thing1 {
    constructor(aThing){
        this.otherThing = aThing;
    }
}
class Thing2 {}

const thing = new Thing1(new Thing2())

//dependency inversion
class Thing1 {
    constructor({
        read = null
    } = {}){
        if(typeof read !== 'function'){
            //establish a simple contract
            throw new TypeError(read + ' is not a function.');
        }
        this._read = read;
        //Somewhere an instance of Thing1()
        //will call this._read()
    }
}

class Thing2 {
    read(){
       //read something
    }
}

const thing2 = new Thing2();
const thing1 = new Thing1({
    read(){
        //Here is the equivalent to the so called "interface"
        return thing2.read();
    }
});
0
Quentin Engles

bubble-di は、JavascriptおよびTypeScript用の軽量のDIコンテナーです。

ファクトリメソッド(コールバック)またはインスタンスを登録できます。以下に簡単な例を示します( その他の例 )。

npm install --save bubble-di

var {DiContainer} = require("bubble-di");
// import { DiContainer } from "bubble-di";

DiContainer.setContainer(new DiContainer());

class Bar { sayBar(){ console.log("bar"); } }
class Baz { sayBaz(){ console.log("baz"); } }
class Foo { 
    constructor (bar, baz)
    {
        bar.sayBar();
        baz.sayBaz();
        // ...
    }
};

DiContainer.getContainer().registerInstance("bar", new Bar());
DiContainer.getContainer().registerInstance("baz", new Baz());
DiContainer.getContainer().register("foo", {
    dependencies: ["bar", "baz"],
    factoryMethod: (bar, baz) => new Foo(bar, baz) },
);
const foo = DiContainer.getContainer().resolve("foo"); // will print "bar" and "baz".
0
Ben

Injectingは軽量でありながら強力なDIコンテナであり、Promiseインジェクションをうまく処理できます。

ホームページの挿入

ソースコード 100行以上。

テストケース の例を参照してください。

0
ssnau

私はJavaScriptが初めてです。 JavaScriptで依存性注入がどのように実装されているのだろうか?インターネットで検索しましたが、何も見つかりませんでした。

JavaScript(主にサーバー側)とエコシステム全体を数年間使用した後、完全に正直に言うと、依存関係の注入(コンテナーは言うまでもありません)が通常のJSプログラマーのツールボックスに実際には組み込まれていないと感じています。それがおそらくそれについての多くの情報がそこにない理由です(しかしそれは良くなっています)。

Javaなどの言語とは異なり、JavaScriptの静的型に依存することはできません。この事実だけでも、インターフェースを介して依存関係を宣言する従来の方法を排除しています。もちろん、JSに型を追加することもできます( Flow を参照)が、これらはコードが実行される前に省略されます。同じことがTypeScriptにも当てはまりますが、型を強制されていないメタデータとして保持する方法があると思います。さらに、JavaScriptは注釈をサポートしていません(ただし、 提案 があります)。

人々はさまざまな方法で制限を回避しています。一部のコンテナは、関数/クラス定義を解析し(渡された関数/クラスで.toString()を呼び出し、結果の文字列を解析します)、名前に基づいて依存関係を探します。依存関係のリストを取得する/ staticメソッド。

Ashley と呼ばれるコンテナに取り組んでいます。これは、バインディングプロセスの一部として依存関係を要求するだけです。さらに検査する必要はありません。

container.instance('Client', Client, ['DependencyA', 'DependencyB']);
container.instance('DependencyA', DependencyA, ['DependencyC']);
container.instance('DependencyB', DependencyB, ['DependencyC']);
container.instance('DependencyC', DependencyC, [], {
  scope: 'Prototype', // Defaults to Singleton
  initialize: true,
  deinitialize: true
});

const client = await container.resolve('Client');

GitHub のその他の例。

0