JavaScript依赖注入

Lev*_*zer 36 javascript dependency-injection

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

yus*_*tas 38

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;
   }
};
Run Code Online (Sandbox Code Playgroud)

我们首先需要一个配置来提供与限定符的必要依赖关系.为此,我们在Injector类中将依赖关系集定义为依赖关系.我们使用依赖集作为我们的容器,它将处理映射到限定符的对象实例.为了使用限定符向依赖集添加新实例,我们定义了一个add方法.接下来,我们定义get方法来检索我们的实例.在这个方法中,我们首先找到arguments数组,然后将这些参数映射到依赖项.之后,我们只使用依赖项构造对象并返回它.有关更多信息和示例,请参阅我博客上的帖子.

  • 到目前为止,对我来说如何在javascript中获取函数的参数名称是一个完全的谜.你的答案在同一时间是可怕和酷的:-) (4认同)
  • 请注意,此示例不会处理箭头函数 (2认同)
  • 如今,大多数代码都经过精简和模糊处理。由于此代码基于作为最小化目标的参数名称,因此该代码不会阻止此类后处理实践。 (2认同)

小智 9

您可以使用AngularJS作为示例.无论是好事,你都必须自己决定.我在一周前写了一篇关于在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.
Run Code Online (Sandbox Code Playgroud)


Alo*_*Bar 6

对我来说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" });
Run Code Online (Sandbox Code Playgroud)

所以我最终得到了以下代码:

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;
    }
};
Run Code Online (Sandbox Code Playgroud)

更新 - 21.5.2018

我已经使用这个解决方案已有几年了.当我将我的代码库移到TypeScript时,解决方案随之发展,以支持TypeScript和JavaScript.经过一段时间,代码在生产中运行,我最近(两天前)发布了一个基于此解决方案的库.随意查看,打开问题等.

薄荷二


Ven*_*esa 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');
Run Code Online (Sandbox Code Playgroud)

用法很简单,用这种方式制作新打印机很容易,但这种打印机不灵活.

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');
Run Code Online (Sandbox Code Playgroud)

因此,我们的打印机类现在更加模块化,清洁易于动摇,但它又不灵活.任何时候你使用new关键字,你实际上是硬编码的东西; 在这种情况下,您正在打印机内构建一个驱动程序,在现实世界中,打印机的一个例子是内置驱动程序,永远不会改变!

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

更好的版本是在我们构建打印机时注入驱动程序,这意味着您可以制作任何类型的打印机,颜色或黑白,因为这次驱动程序是在隔离的情况下制作的,而不是在Printer类之外,然后给出(注意!)进入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.
Run Code Online (Sandbox Code Playgroud)

现在使用情况与用户不同,为了拥有一台打印机,您需要首先构建(制作)一个驱动程序(您选择的!),然后将此驱动程序传递给您的打印机.看起来最终用户现在需要更多地了解系统,但是这种结构使它们具有更高的灵活性.只要有效,用户就可以通过任何驱动程序!例如,假设我们有一个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.
Run Code Online (Sandbox Code Playgroud)

案例4:提供一个setter功能,可以随时设置打印机的驱动程序!

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.
Run Code Online (Sandbox Code Playgroud)

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

  • 做得好!真的很棒的例子!写得很好。 (4认同)
  • 这是我见过的关于这个主题的最好的解释之一,谢谢。 (3认同)
  • 您不应该对常规文本使用“代码标记”,这会降低可读性。 (2认同)
  • 好看又全面 (2认同)
  • 这个答案应该作为构造函数依赖项注入到名为 Life 的“类”中:-) (2认同)