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数组,然后将这些参数映射到依赖项.之后,我们只使用依赖项构造对象并返回它.有关更多信息和示例,请参阅我博客上的帖子.
小智 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)
对我来说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" });
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)
我已经使用这个解决方案已有几年了.当我将我的代码库移到TypeScript时,解决方案随之发展,以支持TypeScript和JavaScript.经过一段时间,代码在生产中运行,我最近(两天前)发布了一个基于此解决方案的库.随意查看,打开问题等.
让我们学习它做一个超级简单的现实世界的例子:)
我将在这里讨论的示例类是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)
依赖注入并不是一个难以理解的概念.这个术语可能有点过载但是一旦你意识到它的目的,你会发现自己大部分时间都在使用它.