我是JavaScript新手。我想知道如何在JavaScript中实现依赖注入?我在互联网上搜索但找不到任何东西。
答案 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回答正是我所需要的!唯一缺少的功能是:
我希望能够做到这样的事情:
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;
}
};
我已经使用这个解决方案已有几年了。当我将我的代码库移到TypeScript时,解决方案随之发展,以支持TypeScript和JavaScript。经过一段时间,代码在生产中运行,我最近(两天前)发布了一个基于此解决方案的库。随意查看,打开问题等。
答案 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
(请参阅underscore或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>
答案 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();
}
});