DI
DI 是一个依赖注入管理模块,Dgeni内部的module通过DI.js管理
Dgeni调用DI部分代码
const dgeniModule = new di.Module()
.value('dgeni', this)
.factory('log', logFactory)
.factory('getInjectables', getInjectablesFactory);
// Create the dependency injection container, from all the packages' modules
const modules = packages.map(pkg => pkg.module);
modules.unshift(dgeniModule);
DI代码入口
进一步到di的源代码出口, 可以看到annotate, Module 和Injector
module.exports = {
annotate: require('./annotation').annotate,
Module: require('./module'),
Injector: require('./injector')
};
annotate
annotate主要有两个函数annotate和parse
函数
- annotate
annotate的最后一个参数需要时注入的方法fn, 其他参数会保存在fn.$inject之内,目前还没有到看有内部使用这个方法的地方。
var annotate = function() {
var args = Array.prototype.slice.call(arguments);
var fn = args.pop();
fn.$inject = args;
return fn;
};
- parse
parse方法会把function转化为字符串,在用正则处理参数。parse方法可以获取function的参数。
// - can't put into "function arg" comments
// function /* (no parenthesis like this) */ (){}
// function abc( /* xx (no parenthesis like this) */ a, b) {}
可以看到parse方法不支持写在funtion同行的注释,这点要注意。
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG = /\/\*([^\*]*)\*\//m;
var parse = function(fn) {
if (typeof fn !== 'function') {
throw new Error('Can not annotate "' + fn + '". Expected a function!');
}
var match = fn.toString().match(FN_ARGS);
return match[1] && match[1].split(',').map(function(arg) {
match = arg.match(FN_ARG);
return match ? match[1].trim() : arg.trim();
}) || [];
};
如果parse调用自己,即parse(parse)
。那么返回的是[ "fn" ]
。但是这个fn不需要时一个可以变成module的依赖,并且这个依赖可以被Inject记录。
Module
Module的代码更简单,输出一个Module类构造函数,内置一个providers存储数组。factory,value,type代码接口一样,都可以返回this用于链式调用。功能就是往providers里加入provider。最后有一个forEach函数用于遍历providers。
注意provider的形式为[name, 'factory' | 'value' | 'type', factory |value|type ]
var Module = function() {
var providers = [];
this.factory = function(name, factory) {
providers.push([name, 'factory', factory]);
return this;
};
this.value = function(name, value) {
providers.push([name, 'value', value]);
return this;
};
this.type = function(name, type) {
providers.push([name, 'type', type]);
return this;
};
this.forEach = function(iterator) {
providers.forEach(iterator);
};
};
module.exports = Module;
Injector
Injector模块使用了上面说到的Module和autoAnnotate模块,最后返回一个var Injector = function(modules, parent)
Injector类。是一个管理使用依赖的模块。
构造函数
var Injector = function(modules, parent)
DI.js使用了原生的Javascript, 且没有定义原型链。所有的属性,方法和构造代码都在这个function里面。这里我们提取构造函数相关的代码部分。
instances.injector = this;
var factoryMap = {
factory: invoke,
type: instantiate,
value: function(value) {
return value;
}
};
modules.forEach(function(module) {
// TODO(vojta): handle wrong inputs (modules)
if (module instanceof Module) {
module.forEach(function(provider) {
var name = provider[0];
var type = provider[1];
var value = provider[2];
providers[name] = [factoryMap[type], value, type];
});
} else if (typeof module === 'object') {
Object.keys(module).forEach(function(name) {
var type = module[name][0];
var value = module[name][1];
providers[name] = [factoryMap[type], value, type];
});
}
});
可以看到之类的modules数组中的module可以有两种形式存在, 最终以[factoryMap方法, value, type]
的格式存储在providers里。
- Module 的instance
构造函数这里会通过Module自身实现的forEach, 遍历其中的provider,形式为[name, 'factory' | 'value' | 'type', factory |value|type ]
, - 普通对象
对于普通的对象,构造函数会遍历她每一个属性,然后使用属性名为name。在Dgeni的Package.ts文件的processor中可以看到这样的对象。
if (typeof processorDef === 'function' ) {
this.module[name] = ['factory', processorDef];
} else {
this.module[name] = ['value', processorDef];
}
属性
Injector并没有外部可以调用的public属性,这里主要讨论它的private属性。
parent
父节点,帮助Inject形成树形接口,方便遍历。currentlyResolving
var currentlyResolving = [];
依赖注入的关键是,调用者不用自己创建实例,实例可以缓存在Inject中。Injector采用惰性加载的方式创建实例,如果方法被调用的时候没有创建实例,Inject会立即创建实例。在这一创建过程中,对象会被添加到currentlyResolving数组中,完成创建后则从currentlyResolving数组移除 。providers
var providers = this._providers = Object.create(parent._providers || null);
存储provider,创建实例的时候使用,以[factoryMap方法, value, type]
的格式存储。instances
var instances = this._instances = Object.create(null);
Object.create
会使用现有的对象来提供新创建的对象的proto。
存储实例factoryMap
var factoryMap = {
factory: invoke,
type: instantiate,
value: function(value) {
return value;
}
};
定义了调用依赖的方式。
- factory调用invoke, 相当于直接调用方法
- type 返回一个实例,相当于new了一个对象=
- value直接返回值
方法
get
var get = function(name)
get方法会返回Instance[name], 如果没有instance有provide,怎会自动实例化一个。name还可以是a.b.c的形式。在invoke函数中,get方法被用来寻找已经记录狗的依赖。instantiate
var instantiate = function(Type) {
var instance = Object.create(Type.prototype);
var returned = invoke(Type, instance);
return typeof returned === 'object' ? returned : instance;
};
实例Type类型, 如果调用invoke能返回对象实例,则返回这个实例。不然会返回一个Object.create创造的instance。
- invoke
var invoke = function(fn, context) {
if (typeof fn !== 'function') {
throw error('Can not invoke "' + fn + '". Expected a function!');
}
var inject = fn.$inject && fn.$inject || autoAnnotate(fn);
var dependencies = inject.map(function(dep) {
return get(dep);
});
// TODO(vojta): optimize without apply
return fn.apply(context, dependencies);
};
invoke方法会查询fn的$inject,就是依赖。没有依赖的话,会通过autoAnnotate自动寻找依赖。这个时候就可以fn的参数命名和依赖一一对应。invoke使用apply返回调用结果。
- createChild
var createChild = function(modules, providersFromParent)
根据当前Injector创建Injector, modules在被使用前会在前部加入将providersFromParent转化为module加入。