上一课,没有讲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// #5388return 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// #5388return 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方法,对页面上的元素节点上的指令进行编译操作。

加油!

转载于:https://www.cnblogs.com/chaojidan/p/4267846.html

AngularJS源码解析2:注入器的详解相关推荐

  1. php get 返回源码,php源码 fsockopen获取网页内容实例详解

    PHP fsockopen函数说明: Open Internet or Unix domain socket connection(打开套接字链接) Initiates a socket connec ...

  2. UVM源码分析之factory机制详解

    前言 作者在学习了一段时间的UVM factory源码之后写下此文,旨在记录自己的学习成果,毕竟好记性不如烂笔头嘛,当然如果能帮助到对这部分有疑惑的同仁就更好了.作者是在笔记本电脑上的windows环 ...

  3. FreeRTOS 之一 源码目录文件 及 移植详解

    写在前面 2018/1/15更新了文章中的部分错误. FreeRTOS源码为最新版的10.0.1.FreeRTOS 10包含两个重要的新功能:流缓冲区和消息缓冲区. 从10.0.0开始,FreeRTO ...

  4. linux设备驱动开发详解源码,linux设备驱动开发详解光盘源码.rar

    压缩包 : linux设备驱动开发详解光盘源码.rar 列表 19/busybox源代码/busybox-1.2.1.tar.bz2 19/MTD工具/mtd-utils-1.0.0.tar.gz 1 ...

  5. Android源码中的目录结构详解

    由于自己从事与底层开发,一开始对项目结构不是很清楚,然后就百度找到了源码结构,我觉得讲的很清楚.Android 2.1源码结构 |-- Makefile |-- bionic              ...

  6. Android进阶——Small源码分析之启动流程详解

    前言 插件化现在已经是Android工程师必备的技能之一,只是学会怎么使用是不行的,所以蹭有时间研究一下Small的源码.对于插件化主要解决的问题是四大组件的加载和资源的加载,读懂所有Small源码需 ...

  7. Android源码之init.rc文件详解

    一.引言 .rc文件是 android系统一个十分重要的文件. 其是资源文件,包括比如对话框.菜单.图标.字符串等资源信息. 使用.rc资源文件的目的是为了对程序中用到的大量的资源进行统一的管理. 本 ...

  8. Java API源码在哪里找_详解查看JAVA API及JAVA源码的方法

    在java的日常学习中,我们有时候会需要看java的api说明,或者是查看java的源码,使我们更好的了解java,接下来我就来说说如何查看java的api以及java源码 对于java的api,一般 ...

  9. Android内核开发:源码的版本与分支详解

    我想很多初学者或许跟我一样,看完Android源码下载相关的文章以后,就开始兴致勃勃地去下载Android源码了,但是下载完了源码后,有没有像我一样产生如下几个困惑呢? (1) Android版本有哪 ...

  10. STL源码剖析---红黑树原理详解下

    转载请标明出处,原文地址:http://blog.csdn.net/hackbuteer1/article/details/7760584       算法导论书上给出的红黑树的性质如下,跟STL源码 ...

最新文章

  1. 把握每天的第一个钟头
  2. Google K8S与阿里Spring Cloud Alibaba相爱相杀,下一个神级架构来了!
  3. 图解系列之JVM运行时数据区
  4. python内存管理变量_Python变量内存管理
  5. cf1132E. Knapsack(搜索)
  6. Mellanox CX-5网卡支持OVS流表加速功能的调研
  7. CUDA TOOlkit Programming Guide K. Unified Memory Programming
  8. 分布式存储引擎OceanBase,UpdateServer 实现机制——存储引擎
  9. python 插入查找
  10. 数学建模(NO.10 典型相关分析)
  11. TikTok运营,TikTok数据分析
  12. 养生秘诀呼吸吐纳真的可以养生嘛?
  13. 使用 FireDAC的 TFDConnection, TFDQuery 最初只能显示50条记录,TDateSet.RecordCount总是获得50的解决方法。
  14. Android 项目必备(十四)--> 开发者选项
  15. 微信表情符号已写入判决书
  16. 怎么设置织梦栏目html结尾,dedecms网站栏目地址url优化成.html结尾的而不是文件夹形式结尾的。请大家来帮忙。...
  17. Android中通过来电转移实现“电话已关机”,“此号码已停机”等
  18. 无线Mesh网络技术基础与应用
  19. DVWA之Brute Force(暴力破解)
  20. 什么是元宇宙,它对未来意味着什么?

热门文章

  1. 利用OpenCV在picture控件中显示图片
  2. Google统治桌面再进一步 欲重走微软称霸之路
  3. 聚焦BCS|北京网络安全大会产业峰会:探寻产业规模增长之道
  4. 谷歌修复多个严重的安卓 RCE 漏洞
  5. 惠普:某些 SSD 驱动的寿命只有32,768 小时(3年多),立即更新!
  6. HDU 2152 Fruit (母函数)
  7. smtplib,发送邮件时的bug
  8. linux常见问题(lrzszvim乱码crontab用户授权chkconfig)
  9. 【PHP基础】文件操作
  10. 【laravel5.4】查询构造器对象与模型instance的互相换换