Angular.js源码分析之注入器
上一篇主要分析了整体的执行流程,最后说到了要执行注入器部分了,下边就一起来看看angular强大的依赖注入机制是怎样一回事。
注入器
执行过程的那部分代码是这样子的:
// 创建注入器
// 隐藏了注入机制
// 详见createInjector函数
var injector = createInjector(modules, config.strictDi);
// 注入这几个服务执行bootstrapApply
injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
function bootstrapApply(scope, element, compile, injector) {
scope.$apply(function() {
// 进入ng环境(上下文)执行
element.data('$injector', injector);
// compile 核心
// compile返回一个publicLink函数
// 然后传入scope直接执行
// 这个scope也就是rootScope
compile(element)(scope);
});
}]
);
首先就要看看createInjector
是个什么鬼?
// 创建注入器实例
function createInjector(modulesToLoad, strictDi) {
strictDi = (strictDi === true);
var INSTANTIATING = {},
providerSuffix = 'Provider',
path = [],
loadedModules = new HashMap([], true),
providerCache = {
$provide: {// $provide服务
provider: supportObject(provider),
factory: supportObject(factory),
service: supportObject(service),
value: supportObject(value),
constant: supportObject(constant),
decorator: decorator
}
},
// $injector服务
providerInjector = (providerCache.$injector =
createInternalInjector(providerCache, function(serviceName, caller) {
if (angular.isString(caller)) {
path.push(caller);
}
throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
})),
// provider实例(调用$get)缓存
instanceCache = {},
instanceInjector = (instanceCache.$injector =
createInternalInjector(instanceCache, function(serviceName, caller) {
var provider = providerInjector.get(serviceName + providerSuffix, caller);
return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
}));
// 加载执行module
// loadModules得到的是所有的runBlocks
// 然后执行runBlocks中内容
forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });
return instanceInjector;
// 省略一些代码 看后续分析
}
createInjector.$$annotate = annotate;
从上往下看,首先看providerCache
对象有$provide
属性,值为对象,提供了provider,factory,service
、value,constant
以及decorator
这些方法,很明显猜到了这里就是angular的$provide
服务的实现部分。
supportObject
这里使用了supportObject
方法,先看看如何实现的:
// supportObject(function(key, val) {})
// 支持对象式传参调用
// 也就是在外边可以类似这样使用
// module.provider({
// aDirective: function() {}
// bDirective: function() {}
// })
function supportObject(delegate) {
return function(key, value) {
if (isObject(key)) {
forEach(key, reverseParams(delegate));
} else {
return delegate(key, value);
}
};
}
其实就是提供了对象式便捷的调用方式。
createInternalInjector
接着往下看,创建了两个注入器实例,一个是providerInjector
一个是instanceInjector
,而该函数的返回值就是这个instanceInjector
。他们是通过createInternalInjector
来创建的注入器实例,分析下代码:
////////////////////////////////////
// internal Injector
////////////////////////////////////
// 创建注入器实例
function createInternalInjector(cache, factory) {
function getService(serviceName, caller) {
// 省略
}
// 执行
function invoke(fn, self, locals, serviceName) {
// 省略
}
// 实例化Type 其实就是constructor去构造
function instantiate(Type, locals, serviceName) {
// 省略
}
return {
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: createInjector.$$annotate,
has: function(name) {
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
}
};
}
看出来注入器实例有五个方法,按照他们的依赖次序分别分析下。
先看annotate
,其实也就是createInjector.$$annotate
,他指向的是一个函数:
// injector的annotate方法
// 主要用于推测注入啥(一个是函数 直接 toString处理)
function annotate(fn, strictDi, name) {
var $inject,
fnText,
argDecl,
last;
if (typeof fn === 'function') {
if (!($inject = fn.$inject)) {
$inject = [];
// 匿名且没有$inject
// 只能toString尝试匹配了
if (fn.length) {
if (strictDi) {
if (!isString(name) || !name) {
name = fn.name || anonFn(fn);
}
throw $injectorMinErr('strictdi',
'{0} is not using explicit annotation and cannot be invoked in strict mode', name);
}
fnText = fn.toString().replace(STRIP_COMMENTS, '');
argDecl = fnText.match(FN_ARGS);
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
arg.replace(FN_ARG, function(all, underscore, name) {
$inject.push(name);
});
});
}
fn.$inject = $inject;
}
} else if (isArray(fn)) {
last = fn.length - 1;
assertArgFn(fn[last], 'fn');
$inject = fn.slice(0, last);
} else {
assertArgFn(fn, 'fn', true);
}
return $inject;
}
可以看出他主要是用来分析注入项的,有三种方式:
-
直接分析函数参数,通过把函数toString了,然后分析其参数得到正确的依赖项
-
给函数的
$inject
属性赋值,是一个数组,就是该函数的依赖项 -
还有就是数组的形式了,也是本人比较推荐的方式,类似于
['$rootScope', function(scope) {}]
这样,数组的前n项都是需要注入的参数,最后一项是一个函数,参数一一对应
再来看下get
也就是getService
方法:
function getService(serviceName, caller) {
// cache中有的话直接返回就好
if (cache.hasOwnProperty(serviceName)) {
if (cache[serviceName] === INSTANTIATING) {
throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
serviceName + ' <- ' + path.join(' <- '));
}
return cache[serviceName];
} else {
try {
path.unshift(serviceName);
cache[serviceName] = INSTANTIATING;
// 调用factory创建实例(provider或instance)
return cache[serviceName] = factory(serviceName, caller);
} catch (err) {
if (cache[serviceName] === INSTANTIATING) {
delete cache[serviceName];
}
throw err;
} finally {
path.shift();
}
}
}
可以看到主要逻辑就是通过factory
来得到对应的service的值,这里主要看下instanceInjector
实例创建的时候传入的factory
函数所做的事情:
instanceInjector = (instanceCache.$injector =
createInternalInjector(instanceCache, function(serviceName, caller) {
// 需要得到的是实例,首先要获取provider,然后调用provider的$get得到真正的实例
var provider = providerInjector.get(serviceName + providerSuffix, caller);
return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
}));
下一步就来分析下这个invoke
执行的具体代码:
// 执行
function invoke(fn, self, locals, serviceName) {
if (typeof locals === 'string') {
serviceName = locals;
locals = null;
}
var args = [],
// 依赖项
$inject = createInjector.$$annotate(fn, strictDi, serviceName),
length, i,
key;
for (i = 0, length = $inject.length; i < length; i++) {
key = $inject[i];
if (typeof key !== 'string') {
throw $injectorMinErr('itkn',
'Incorrect injection token! Expected service name as string, got {0}', key);
}
args.push(
locals && locals.hasOwnProperty(key)
? locals[key]
: getService(key, serviceName)
);
}
if (isArray(fn)) {
fn = fn[length];
}
// http://jsperf.com/angularjs-invoke-apply-vs-switch
// #5388
return fn.apply(self, args);
}
逻辑就是得到需要注入的依赖项$inject
,然后再得到每一个依赖项的值存到args
中,最后执行fn
函数,传入各个依赖项。
下边再来看一下instantiate
:
// 实例化Type 其实就是constructor去构造
function instantiate(Type, locals, serviceName) {
// 省略
// Check if Type is annotated and use just the given function at n-1 as parameter
// e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
// Object creation: http://jsperf.com/create-constructor/2
var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
var returnedValue = invoke(Type, instance, locals, serviceName);
return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
}
其实也是调用的invoke
,只不过是以Type
构造函数实例(原型继承)为this
来执行函数Type
,也就是说是以构造函数实例化的方式来执行实例化的。可以看到如果执行Type
构造函数返回的结果如果是函数或者是函数的话返回就是该结果,否则的话就是返回刚刚创建的实例。
最后一个has
简单多了,直接判断了在providerCache
中有没有或者是cache
(判断到的是instanceCache
)中有没有。
到这里createInternalInjector
就分析完了,创建出来的每个注入器实例都有上边看到的五个方法:invoke
, instantiate
, get
, annotate
, has
loadModules
这是一个真正执行依赖项的方法(不包含run
的,run
的是返回值,因为run
是在最后执行的),很重要,仔细来看:
// 其实更像是一个执行module上的$provider和$injector依赖
function loadModules(modulesToLoad) {
assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array');
var runBlocks = [], moduleFn;
forEach(modulesToLoad, function(module) {
if (loadedModules.get(module)) return;
loadedModules.put(module, true);
function runInvokeQueue(queue) {
var i, ii;
for (i = 0, ii = queue.length; i < ii; i++) {
var invokeArgs = queue[i],
provider = providerInjector.get(invokeArgs[0]);
provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
}
}
try {
if (isString(module)) {
// moduleFn就是Module实例对象 不明白为啥会起了个Fn名字
moduleFn = angularModule(module);
// 合并所有的runBlocks
// 是一个递归调用过程
// 把依赖模块也一并执行了
runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
// 执行$provider的指定方法,如factory,value等
runInvokeQueue(moduleFn._invokeQueue);
// 执行$injector的invoke方法
runInvokeQueue(moduleFn._configBlocks);
} else if (isFunction(module)) {
runBlocks.push(providerInjector.invoke(module));
} else if (isArray(module)) {
runBlocks.push(providerInjector.invoke(module));
} else {
assertArgFn(module, 'module');
}
} catch (e) {
if (isArray(module)) {
module = module[module.length - 1];
}
if (e.message && e.stack && e.stack.indexOf(e.message) == -1) {
// Safari & FF's stack traces don't contain error.message content
// unlike those of Chrome and IE
// So if stack doesn't contain message, we create a new string that contains both.
// Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
/* jshint -W022 */
e = e.message + '\n' + e.stack;
}
throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}",
module, e.stack || e.message || e);
}
});
return runBlocks;
}
从上边的代码可以看出,在整个执行过程中是先执行的_invokeQueue
,再执行的_configBlocks
,为啥是这样的顺序,不能反过来执行吗?他俩分别是执行的啥?
下边来解答整个疑问,需要先看第二个问题(他俩分别执行了啥?),先看_invokeQueue
中存放的是啥。
在上一篇中分析angular.module
的执行逻辑的时候,看到返回实例的provider
,factory
,service
,value
,constant
,decorator
,animation
,filter
,controller
,directive
这些方法,他们真正执行的代码其实是往_invokeQueue
中存放类似于[provider, method, arguments]
这样的数组对象进去的(当然不是立即执行的,被别的依赖了之后再去执行),所以说执行_invokeQueue
也就是相当于先去执行得到上边的这些方法的过程,其实也就是调用对应的Provider
对应的方法的过程。
在看_configBlocks
,保存的配置逻辑代码块,同样是在angular.module
的定义中有这样的代码var config = invokeLater('$injector', 'invoke', 'push', configBlocks);
,也就是说module的config函数其实是可以依赖注入的,可以写入依赖项(虽然只能是常量 provider可以注入)。
到这里就明了了,如果说执行_invokeQueue
和_configBlocks
的顺序反过来的话,那么当前模块的常量和provider是不能注入到config中的。
下边来个例子来说明下这个过程:
// 注意为啥这里注入的不是myService 而是myServiceProvier ,请看下边关于provider的分析
var app = angular.module('app', [], ['myServiceProvier', function appConfig(myServiceProvier) {}]);
app.factory('myService', ['$http', function($http) {}]);
app.run(['$rootScope', function($rootScope) {}])
首先执行了factory
,此时只是往app._invokeQueue
中插入了新的项['$provider', 'factory', [['myService', ['$http', function($http) {}]]]]
,,而默认定义module的时候传入的
appConfig函数则是被push进了
app._configBlocks中了,插入的项就是
[‘$injector’, ‘invoke’, [[‘myServiceProvier’, function appConfig(myServiceProvier) {}]]]`。
再执行run
方法,其实只是往app._runBlocks
中增加了新的项['$rootScope', function($rootScope) {}]
。
现在来看当loadModules
执行到了app
模块的时候是怎样的过程:
-
moduleFn
就是app
-
requires
无依赖,这里就忽略,然后得到runBlocks
,长度是1,里边的内容就是['$rootScope', function($rootScope) {}]
-
执行
_invokeQueue
,他的长度是1,到runInvokeQueue
函数中,invokeArgs就是['$provider', 'factory', [['myService', ['$http', function($http) {}]]]]
,所以实际的执行就相当于$provider.factory(['myService', ['$http', function($http) {}]])
,然后$provider.factory
其实就是后边会分析的在createInjector
函数中的factory
函数,作用也就是给providerCache
对象设置myServiceProvier
的值为{$get: ['$http', function($http) {}]}
-
继续执行
_configBlocks
,长度也是1,到runInvokeQueue
函数中,invokeArgs就是['$injector', 'invoke', [['myServiceProvier', function appConfig(myServiceProvier) {}]]]
,实际调用的就是$injector.invoke(['myServiceProvier', function appConfig(myServiceProvier) {}]])
,这个函数上边分析过,过程就是找到依赖项myServiceProvier
(此时在上一步的时候就已经给providerCache
设置过myServiceProvier
了),然后执行appConfig
。
到这里,复杂的loadModules
分析就算是完成了,此时记得他的返回值,也就是runBlocks
的内容['$rootScope', function($rootScope) {}]
。
createInjector
回到createInjector
中,拿到loadModules
的返回值后,直接循环,这样执行了:
instanceInjector.invoke(fn)
通过instanceInjector
这个注入器直接执行了,也就是调用invoke
方法,记得上边的runBlocks
的内容['$rootScope', function($rootScope) {}]
吗,也就是说传入invoke
的就是他。逻辑不多说了,已经分析过了。
不要忘记在createInjector
中这些函数:provider
,factory
,service
,value
,constant
,decorator
还没有分析,下边一一来看。
首先看看provider
函数的实现:
// 构造名字为name的provider
function provider(name, provider_) {
assertNotHasOwnProperty(name, 'service');
if (isFunction(provider_) || isArray(provider_)) {
// 是函数或者数组
// 其实数组也是函数 只是注入写法问题
provider_ = providerInjector.instantiate(provider_);
}
if (!provider_.$get) {
throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name);
}
return providerCache[name + providerSuffix] = provider_;
}
其实很简单,就是给providerCache
设置值的,只不过在设置的时候会加上providerSuffix
也就是Provider
,所以说,在你需要注入一些provider
的时候,名字后边是要加上Provider
的。
接着看factory
:
// factory其实就是provider的简写方式
// 省略了$get那一层而已
// enforce是否强制返回值 如果说不是false的话 那么就会去强制
// 必须有 没有的话 就会报错
//
// 注意这里的factory和真正咱们在外边
// 通过module的实例调用factory的区别,
// 通过实例调用factory的时候其实是通过supportObject包装
// 过的,而包装过得函数调用时最多只有两个参数
// 所以是根本不会存在enforce配置项的
function factory(name, factoryFn, enforce) {
return provider(name, {
$get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
});
}
注释已经写的很清楚了,就是provider简写方法而已。
然后看service
:
function service(name, constructor) {
return factory(name, ['$injector', function($injector) {
// 参看instantiate逻辑
return $injector.instantiate(constructor);
}]);
}
其实就是对于以构造函数实例化的形式来使用factory
的,举一个例子:
如果用factory
可能这样写:
module.factory('xx', ['$http', function($http) {
return {
a: function() {}
}
}]);
如果用service
的话,可能就是这样:
module.service('xx', ['$http', function($http) {
this.a = function() {};
}]);
再看value
:
function value(name, val) { return factory(name, valueFn(val), false); }
简单的对factory
封装,只需要传入name和val,不必包含函数部分,在依赖的时候,其值就是val。
下边看constant
:
// 常量 直接写入缓存对象
// 所以是在provider和instance(run)中都是
// 可以住人的
function constant(name, value) {
assertNotHasOwnProperty(name, 'constant');
providerCache[name] = value;
instanceCache[name] = value;
}
所以在任何时候都是可以去注入常量的。这里并看不出来为啥会有常量的效果,真正的魔法在模块实例化对象那里:
// 注意这里是怎么实现常量效果的
// 通过给 invokeQueue unshift
// $provide的constant方法
// 所以是一个逆序的过程
// [后边设定的值,最开始设定的值]
// 所以说执行完之后 就是得到的 最开始设定的那个值
constant: invokeLater('$provide', 'constant', 'unshift')
然后这个api是暴露在module的实例上的。
最后看下decorator
:
// 装饰器
// 也就是保存原来的$get
// 然后重新修改下
function decorator(serviceName, decorFn) {
var origProvider = providerInjector.get(serviceName + providerSuffix),
orig$get = origProvider.$get;
origProvider.$get = function() {
var origInstance = instanceInjector.invoke(orig$get, origProvider);
// 把之前的实例当做locals传入到invoke中
return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
};
}
invoke
angular初始化是要进行编译的,那编译的调用也就在createInjector
之后,看代码:
// 注入这几个服务执行bootstrapApply
injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
function bootstrapApply(scope, element, compile, injector) {
// 编译逻辑
}]
);
可以看出拿通过createInjector
创建的注入器实例来执行bootstrapApply
代码,当然会继续走上边以及分析过的invoke
逻辑,这里不再细说。但是这里看依赖注入的参数,这里出现了一个关键的$rootScope
,他是angular中一个极其重要的东西,留到下篇分析。
结语
本篇主要是分析了整个注入器原理以及过程,希望对其没有深入了解的你有所帮助。当然,更欢迎讨论、吐槽!