上一课,没有讲createInjector方法,只是讲了它的主要作用,这一课,详细来讲一下这个方法。此方法,最终返回的注册器实例对象有以下几个方法:

invoke,
instantiate,
get,
annotate,
has。

function createInjector(modulesToLoad) {
var INSTANTIATING = {},
providerSuffix = 'Provider',
path = [],
loadedModules = new HashMap(), //一个hash对象,用来存储已经加载过的模块
providerCache = {
$provide: {
provider: supportObject(provider),
factory: supportObject(factory),
service: supportObject(service),
value: supportObject(value),
constant: supportObject(constant),
decorator: decorator
}
},
providerInjector = (providerCache.$injector =
createInternalInjector(providerCache, function() { //创建一个内部的注册器实例对象,它跟下面的注册器实例对象拥有相同的方法,只是它们操作的参数不一样,这个操作的是providerCache和抛出错误的回调函数。
throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
})),
instanceCache = {},
instanceInjector = (instanceCache.$injector =
createInternalInjector(instanceCache, function(servicename) { //创建一个内部的注册器实例对象,并返回此注册器实例对象,此对象有几个方法,它的这几个方法里面有引用你通过createInternalInjector传进去的参数,因此,形成了闭包。
var provider = providerInjector.get(servicename + providerSuffix);
return instanceInjector.invoke(provider.$get, provider);
})
       );
forEach(loadModules(modulesToLoad), //加载初始化的模块
        function(fn) { instanceInjector.invoke(fn || noop); }
     );
return instanceInjector; //返回注册器实例对象
function supportObject(delegate) {
return function(key, value) {
if (isObject(key)) {
forEach(key, reverseParams(delegate));
} else {
return delegate(key, value);
}
};
}
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_;
}
function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); }
function service(name, constructor) {
return factory(name, ['$injector', function($injector) {
return $injector.instantiate(constructor);
}]);
}
function value(name, val) { return factory(name, valueFn(val)); }
function constant(name, value) {
assertNotHasOwnProperty(name, 'constant');
providerCache[name] = value;
instanceCache[name] = value;
}
function decorator(serviceName, decorFn) {
var origProvider = providerInjector.get(serviceName + providerSuffix),
orig$get = origProvider.$get;
origProvider.$get = function() {
var origInstance = instanceInjector.invoke(orig$get, origProvider);
return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
};
}
function loadModules(modulesToLoad){ //这里的modulesToLoad = ["ng", ["$provide",function($provide){ $provide.value("$rootElement", element); }]]。
var runBlocks = [], moduleFn, invokeQueue, i, ii;
forEach(modulesToLoad, function(module) { //针对每一个模块,执行此方法
if (loadedModules.get(module)) return; //如果此模块已经加载过了,就不用再次加载了,直接进行下一次循环去加载下一个模块
loadedModules.put(module, true);
try {
if (isString(module)) { //ng模块进入到这里
moduleFn = angularModule(module); //得到ng模块的实例对象
runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) {
var invokeArgs = invokeQueue[i],
provider = providerInjector.get(invokeArgs[0]);
provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
}
} 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;
}
function createInternalInjector(cache, factory) { //创建一个内部的注入器实例对象
function getService(serviceName) {
if (cache.hasOwnProperty(serviceName)) {
if (cache[serviceName] === INSTANTIATING) {
throw $injectorMinErr('cdep', 'Circular dependency found: {0}', path.join(' <- '));
}
return cache[serviceName];
} else {
try {
path.unshift(serviceName);
cache[serviceName] = INSTANTIATING;
return cache[serviceName] = factory(serviceName);
} catch (err) {
if (cache[serviceName] === INSTANTIATING) {
delete cache[serviceName];
}
throw err;
} finally {
path.shift();
}
}
}
function invoke(fn, self, locals){
var args = [],
$inject = annotate(fn),
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)
);
}
if (!fn.$inject) {
// this means that we must be an array.
fn = fn[length];
}
// http://jsperf.com/angularjs-invoke-apply-vs-switch
// #5388
return fn.apply(self, args);
}
function instantiate(Type, locals) {
var Constructor = function() {},
instance, returnedValue;
// 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) {}]);
Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype;
instance = new Constructor();
returnedValue = invoke(Type, instance, locals);
return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
}
return {
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: annotate,
has: function(name) {
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
}
};
}
}

首先,在这个函数体内部,有几个创建服务提供者(provider)的几个方法:provider,service,factory,value。反观这几个方法,它们内部的实现调用的都是provider方法,而且所有的provider都必须包含一个$get属性,因此你自定义一个provider时,必须有$get属性。当然,如果你通过service,factory等方法创建的话,你不需要显式的写$get属性,因为这些方法的内部实现都会增加一个$get属性。

然后,在函数体内部,除了创建provider的方法外,还有一个创建注入器的方法createInternalInjector,返回的注入器主要用来创建provider真正的实例对象,并注入给你的应用,同时处理相关的依赖创建。

然后,在函数体的内部,有几个内部变量,一个是providerCache,这个会保存一个$provide属性对象,此属性对象主要用来对外提供创建服务提供者(provider)的方法,同时,providerCache会保存所有已经注入进来的provider(在publishExternalAPI方法中,有一段这样的代码)。

 $provide.provider({          //在ng模块中,定义一系列的服务提供者
$animate: $AnimateProvider,
$controller: $ControllerProvider,
$filter: $FilterProvider,
$http: $HttpProvider,
$location: $LocationProvider,
$parse: $ParseProvider,
$rootScope: $RootScopeProvider,
$window: $WindowProvider
});

同时,providerCache还有一个$injector属性对象,此属性对象就是一个注入器实例。它跟另一个变量providerInjector指向的是同一个注入器实例对象。

instanceCache这个变量,会保存所有已经实例化的provider对象,同时,这个变量的$injector属性值是一个注入器实例。它跟另外一个变量instanceInjector指向的是同一个注入器实例对象,这个注入器实例对象是用来真正实例化一个provider的。

然后,在函数体内部,有一个loadModules方法,angularjs就是依靠这个方法来加载模块,以及模块所依赖的provider。loadModules函数体中,会依次加载模块数组里对应的provider(forEach方法遍历数组),如果模块数组中的值是字符串,就会直接创建provider实例对象。如果不是,就会把此模块的provider信息push到数组runBlocks中,然后通过instanceInjector来进行实例化操作。

在创建provider实例对象时,代码如下:

var invokeArgs = invokeQueue[i],provider = providerInjector.get(invokeArgs[0]);

provider[invokeArgs[1]].apply(provider, invokeArgs[2]);

上面代码的意思是:通过注入器得到对应的服务提供者provider,然后调用服务提供者的实例化方法,创建这个服务的实例对象。

instanceInjector和providerInjector这两个变量都是注入器实例对象,它们之间的区别在于调用createInternalInjector方法创建它们时,传入的第二个参数。当要真正的实例化provider的时候,第二个参数负责真正的初始化providerCache的保存的provider,其实就是执行provider的$get方法,然后把值保存到instanceCache中,之所以用缓存对象保存实例出来的provider对象,是为了实现单例操作。providerInjector注入器,传入的第二个参数是一个空函数,不会实例化provider对象,而instanceInjector注入器,传入的第二个参数是一个实例化provider对象的函数。

function(servicename) {
var provider = providerInjector.get(servicename + providerSuffix);
return instanceInjector.invoke(provider.$get, provider);
}

上面的代码就是创建instanceInjector注入器时,传入的第二个参数,这个函数的意思是:先在providerCache里查找此服务提供者provider,然后把此provider的$get方法传给instanceInjector的invoke方法,最后生成这个provider的实例对象。

最后,讲一下注入器的invoke方法:

function invoke(fn, self, locals){
var args = [],
$inject = annotate(fn),
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)
);
}
if (!fn.$inject) {
// this means that we must be an array.
fn = fn[length];
} // http://jsperf.com/angularjs-invoke-apply-vs-switch
// #5388
return fn.apply(self, args);
}

此函数体内,会调用一个方法annotate,此方法是获取一个函数的参数,并以数组形式返回。invoke会自动检查要执行的函数的参数,假如这些参数已经生成实例对象了,就传给要执行的函数,否则先生成依赖的服务的实例对象,然后传给要执行的函数。

在bootstrap方法中,创建注入器的代码执行结束后,就会调用注入器的invoke方法:

injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',
function(scope, element, compile, injector, animate) {
scope.$apply(function() {
element.data('$injector', injector);
compile(element)(scope);
});
}]
);
return injector;

invoke方法,就会生成数组中'$rootScope', '$rootElement', '$compile', '$injector', '$animate'的实例对象,传入回调函数function中,回调函数执行时,就可以调用这些服务实例对象了。在回调函数中,就会调用angular的compile方法对页面上的指令进行编译操作了。

最后,我们总结下angular初始化的过程:

在页面上加载angularJS库,浏览器进行下载,下载完成后,执行。angularJS源代码是一个立即执行函数,因此执行此立即执行函数,这样就不会污染全局环境。

然后,初始化一个window.angular = {},定义一系列的对象和方法。然后绑定jQuery,如果页面上有引入jQuery,那么angular.element = jQuery,否则就用angular内部定义的JQLite赋给angular.element。

然后,调用publishExternalAPI方法,扩展angular对象的属性方法,然后创建模块加载器,也就是angular.module方法,此模块加载器可以创建模块的实例对象。然后利用此模块加载器,加载ng等内置模块,然后此ng模块中,创建$compile服务提供者,然后定义一系列的内置指令,以及一系列的内置服务提供者。

最后调用

 jqLite(document).ready(function() {
angularInit(document, bootstrap);
}

此方法angularInit会等到文档加载完成后才会执行,以防页面没加载完成,你就开始编译页面上的元素节点上的指令。

此方法,会查找页面上是否有ng-app指令,如果有,就进行angular应用的启动操作:执行bootstrap方法。

然后,bootstrap方法会调用内部的定义的doBootstrap方法。

doBootstrap首先调用createInjector方法创建注入器,同时,createInjector方法也会加载angular内置的服务提供者和模块,以及实例化一些服务提供者对象等等。

然后调用注入器的invoke方法,来实例化服务的实例对象,然后注入到回调函数中去。

最后,执行回调函数,此回调函数中,会调用compile方法,对页面上的元素节点上的指令进行编译操作。

加油!

AngularJS源码解析2:注入器的详解的更多相关文章

  1. guava-retrying 源码解析(停止策略详解)

    一.停止策略相关类 1.停止策略接口:StopStrategy接口,只有一个抽象方法 // 是否应该停止重试.不同的停止策略有不同的实现.boolean shouldStop(Attempt fail ...

  2. guava-retrying 源码解析(等待策略详解)

    一.等待策略相关类: 1.等待策略接口:WaitStrategy接口 该接口只有一个方法,就是返回尝试失败之后,下一次尝试之前的等待时间.long computeSleepTime(Attempt f ...

  3. vuex 源码解析(三) getter属性详解

    有时候我们需要从store中的state中派生出一些状态,例如: <div id="app"> <p>{{reverseMessage}}</p> ...

  4. jQuery 源码解析(三) pushStack方法 详解

    该函数用于创建一个新的jQuery对象,然后将一个DOM元素集合加入到jQuery栈中,最后返回该jQuery对象,有三个参数,如下: elems Array类型 将要压入 jQuery 栈的数组元素 ...

  5. guava-retrying 源码解析(阻塞策略详解)

    这是一种策略,用于决定重试者应如何在重试尝试之间进行阻止.通常这只是一个thread.sleep(),但是如果需要的话,实现可能更复杂. 一.阻塞策略相关的类或接口 1.阻塞策略接口:BlockStr ...

  6. AngularJS源码解析4:Parse解析器的详解

    $ParseProvider简介 此服务提供者也是angularjs中用的比较多的,下面我们来详细的说下这个provider. function $ParseProvider() { var cach ...

  7. object -c OOP , 源码组织 ,Foundation 框架 详解1

     object -c  OOP ,  源码组织  ,Foundation 框架 详解1 1.1 So what is OOP? OOP is a way of constructing softwar ...

  8. AngularJS源码解析3:RootScope的创建过程

    RootScopeProvider简介 RootScopeProvider是angularjs里面比较活跃的一个provider.它主要用来生成实例rootScope,它代表angularjs应用的根 ...

  9. Spring IOC容器启动流程源码解析(一)——容器概念详解及源码初探

    目录 1. 前言 1.1 IOC容器到底是什么 1.2 BeanFactory和ApplicationContext的联系以及区别 1.3 解读IOC容器启动流程的意义 1.4 如何有效的阅读源码 2 ...

  10. AngularJS源码解析1:angular自启动过程

    angularJS加载进来后,会有一个立即执行函数调用,在源代码的最下面是angular初始化的地方.代码展示: bindJQuery(); publishExternalAPI(angular); ...

随机推荐

  1. C语言高级程序设计——进制算法以及位算符号

    语言不够官方:意会: 数据储存运算是以二进制的,二进制数有原码 反码 补码三种.通常所说的二进制就是原码.(语言不官方) 原码 :4的原码可以为:0000 0100:最高位0 可以为符号数 反码:正数 ...

  2. Visual Studio C++ include与library

    首先介绍几种目录: 1. 系统路径 系统路径在vc中是"Properties->Configuration Properties -> VC++ Directories" ...

  3. ArcGIS GP服务的发布及调用

    参考https://www.jianshu.com/p/5331fa708fe5

  4. Marshal.FreeHGlobal 方法 (IntPtr)

    释放以前从进程的非托管内存中分配的内存. 命名空间:   System.Runtime.InteropServices程序集:  mscorlib(位于 mscorlib.dll) 下面的示例演示如何 ...

  5. jdeveloper 恢复默认配置

    1>jdeveloper的环境设置出现问题,恢复默认的配置,需要删除保存再登录账户中的配置文件,以达到恢复默认配置的目的.只需删除以下配置文件目录即可. C:\Users\当前登录用户名\App ...

  6. Laravel中Trait的用法实例详解

    本文实例讲述了Laravel中Trait的用法.分享给大家供大家参考,具体如下: 看看PHP官方手册对Trait的定义: 自 PHP 5.4.0 起,PHP 实现了代码复用的一个方法,称为 trait ...

  7. Debian Mount nfs 出错的解决

    系统未安装 nfs 客户端 #aptitude install nfs-common 解决!

  8. Redis数据结构(四)

    存储list: list存储方式采用头和尾插入的方式,这样效率快,如果没有这个插入的数据,redis自己会创建这个数据,如果是中间插入的话,采用list方式效率就会很慢. ArrayList使用数组方 ...

  9. ZOJ3704 I am Nexus Master! 2017-04-06 23:36 56人阅读 评论(0) 收藏

    I am Nexus Master! Time Limit: 2 Seconds      Memory Limit: 65536 KB NexusHD.org is a popular PT (Pr ...

  10. An Introduction to Text Mining using Twitter Streaming

    Text mining is the application of natural language processing techniques and analytical methods to t ...