JavaScript依赖注入

时间:2013-11-18 21:33:13

标签: javascript dependency-injection

我是JavaScript新手。我想知道如何在JavaScript中实现依赖注入?我在互联网上搜索但找不到任何东西。

11 个答案:

答案 0 :(得分:36)

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

答案 1 :(得分:9)

您可以使用AngularJS作为示例。无论是好事,你都必须自己决定。我在一周前写过一篇 article about demistifying dependency injection in 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.

答案 2 :(得分:6)

对我来说yusufaytas回答正是我所需要的!唯一缺少的功能是:

  1. 获取自定义参数的依赖关系。
  2. 使用回调注册依赖项。
  3. 我希望能够做到这样的事情:

    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。经过一段时间,代码在生产中运行,我最近(两天前)发布了一个基于此解决方案的库。随意查看,打开问题等。

    peppermint-di

答案 3 :(得分:6)

让我们通过一个超级简单的现实世界示例来学习它:)

我将在这里讨论的示例类是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');

用法很简单,用这种方法制造新打印机很容易,但是这种打印机不灵活。

案例2: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类现在更难以理解了,它们是 modular clean easy ,但又不灵活。每当您使用new关键字时,您实际上就是在进行硬编码;在这种情况下,您正在构造打印机中的驱动程序,在现实世界中,这是打印机的示例,该打印机带有永远不变的内置驱动程序!

案例3:将已经制作好的驱动程序注入打印机中

更好的版本是在构造打印机时注入驱动程序 这意味着您可以制造任何类型的打印机,彩色或黑白打印机,因为这 在隔离类和在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.

使用情况现在有所不同,作为用户,要拥有打印机,您首先需要 构造(制作)一个驱动程序(您选择!),然后将该驱动程序传递给打印机。最终用户现在似乎需要更多地了解系统,但是这种结构使他们更具灵活性。用户只要有效就可以通过ANY驱动程序!例如,假设我们有一个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.

依赖注入并不是一个很难理解的概念。该术语可能有点过载,但是一旦您意识到它的目的,您就会发现自己大部分时间都在使用它。

答案 4 :(得分:2)

我认为DI是JS / ES2015的开箱即用功能。 :-)当然,它不是功能齐全的IOC容器,但看起来很有用,不是吗?看看下面的例子!

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(请参阅underscorelodash)中,将其转换为单例。

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>

答案 5 :(得分:1)

看看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');

答案 6 :(得分:1)

我编写了自己的名为Di-Ninja的JavaScript依赖注入框架 https://github.com/di-ninja/di-ninja

它功能齐全,是目前javascript中唯一一个实现Composition-Root设计模式的方法, 帮助您将所有事物分离,并在一个独特的根位置连接应用程序组件和配置。 http://blog.ploeh.dk/2011/07/28/CompositionRoot/

它适用于NodeJS和Webpack

任何反馈都将不胜感激

答案 7 :(得分:0)

candiJS是一个轻量级的隐式依赖注入和对象创建库。 Have a look

示例:

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');

答案 8 :(得分:0)

bubble-di是Javascript和Typescript的轻量级DI容器。

它使您能够注册工厂方法(回调)或实例。以下是一个简单示例(more examples)。

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".

答案 9 :(得分:0)

  

我是JavaScript新手。我想知道如何在JavaScript中实现依赖注入?我搜索了互联网但却找不到任何东西。

在使用JavaScript(主要是在服务器端)和整个生态系统几年之后,我完全诚实地认为依赖注入(更不用说容器)并没有真正成为一个常规JS程序员的工具箱。这可能就是为什么那里没有太多关于它的信息的原因(虽然它已经好转了)。

与Java等语言相反,您不能依赖JavaScript中的静态类型。仅这一事实就排除了通过接口声明依赖关系的传统方式。您当然可以向JS添加类型(请参阅Flow),但在代码执行之前,这些类型会被省略。这同样适用于TypeScript,但我相信有一种方法可以将类型保存为非强制元数据。此外,JavaScript不支持注释(尽管它有proposal)。

人们已经以各种方式克服了局限。一些容器解析函数/类定义(就像它们在传递的函数/类上调用.toString()并解析生成的字符串)并根据名称查找依赖项,一些需要函数/类来提供属性/静态获取依赖项列表的方法。

我一直在一个名为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的更多示例。

答案 10 :(得分:0)

即使这是一个老问题,我也感受到了这种冲动。 ;)

//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();
    }
});