JavaScript Dependency Injection

2019-01-29 19:53发布

I am new at JavaScript. I wonder how dependency injection is being implemented in JavaScript? I searched the internet but couldn't find anything.

12条回答
爱情/是我丢掉的垃圾
2楼-- · 2019-01-29 20:03
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;
   }
};

The first thing we need a configuration to provide necessary dependencies with qualifiers. To do that, we define a dependency set as dependencies in the Injector class. We use dependency set as our container which will take care of our object instances mapped to qualifiers. In order to add new instance with a qualifier to dependency set, we define an add method. Following that, we define get method to retrieve our instance. In this method, we first find the arguments array and then map those arguments to dependencies. After that, we just construct the object with our dependencies and return it. For more information and examples, please see the post on my blog.

查看更多
叛逆
3楼-- · 2019-01-29 20:04

You can use AngularJS as an example. Whether it is a good thing, you have to decide for yourself. I wrote a week ago an article about demistifying dependency injection in AngularJS. Here you can read the code from the article:

// 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.
查看更多
霸刀☆藐视天下
4楼-- · 2019-01-29 20:04

candiJS is a lightweight implicit dependency injection and object creation library. Have a look

Example:

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');
查看更多
等我变得足够好
5楼-- · 2019-01-29 20:05

Let's learn it doing a super simple real world example :)

The example class I am going to talk about here is a Printer which needs a driver to print something. I have demonstrated the advantages of dependency injection design pattern in 4 steps to arrive at the best solution in the end.

Case1: no dependency injection used:

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

usage is simple, it is easy to make a new printer this way but this printer is not flexible.

Case2: abstract the functionalities inside the print method into a new class called 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');

So our Printer class is now more modular, clean and easy to undrestand but It is not flexible yet again. Any time you use new keyword you are actually hard-coding something; in this case you are constructing a driver inside your Printer which in real world is an example of a printer that comes with a built-in driver that can never change!

Case3: Inject an already made driver into your printer

A better version is to inject a driver at the time we construct a printer meaning you can make any type of printer, color or black&white, because this time the driver is being made in isolation and outside the Printer class and then given (INJECTED!) into the 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.

usage is now different, as a user, in order to have a printer you need to first construct (make) a driver (of your choice!) and then pass this driver to your Printer. It may seem that end user now needs to know a bit more about the system, however this structure gives them more felexibility. Users can pass ANY driver as long as valid! for example let's say we have a BWDriver (black & white) type of driver; user can create a new driver of this type and use that to make a new Printer that prints black and white.

So far so good! But what you think we can do better and what you think has still some room to address here?! I am sure you can see it too!

We are creating a new printer each time we need our printer to print with a different driver! That is because we are passing our driver of choice to the Printer class at the construction time; if user wants to use another driver they need to create a new Printer with that driver; for example if now I want to do a color print I need to do:

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.

Case4: Provide a setter function to set the driver of your printer at ANY TIME!

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.

Dependency Injection is not a really difficult concept to understand. The term may be a bit overloaded but once you have realised its purpose you will find yourself using it most of the time.

查看更多
太酷不给撩
6楼-- · 2019-01-29 20:06

I'd say DI is an out-of-the-box feature of JS/ES2015. :-) Of course, it is not full featured IOC containers but looks useful, doesn't it? Check out an example below!

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

You can wrap dependencies in _.once (see underscore or lodash) to turn them into singletons.

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>

查看更多
甜甜的少女心
7楼-- · 2019-01-29 20:07

I coded my own JavaScript Dependency Injection Framework called Di-Ninja https://github.com/di-ninja/di-ninja

It's full featured and is currently the only one in javascript, as I know, that implement Composition-Root design pattern, helping you to keep all things decoupled and to wire application components and config at one unique root place. http://blog.ploeh.dk/2011/07/28/CompositionRoot/

It work well with NodeJS and Webpack

Any feedback would be appreciated

查看更多
登录 后发表回答