AngularJS源码解析2:注入器的详解
上一课,没有讲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:注入器的详解相关推荐
- php get 返回源码,php源码 fsockopen获取网页内容实例详解
PHP fsockopen函数说明: Open Internet or Unix domain socket connection(打开套接字链接) Initiates a socket connec ...
- UVM源码分析之factory机制详解
前言 作者在学习了一段时间的UVM factory源码之后写下此文,旨在记录自己的学习成果,毕竟好记性不如烂笔头嘛,当然如果能帮助到对这部分有疑惑的同仁就更好了.作者是在笔记本电脑上的windows环 ...
- FreeRTOS 之一 源码目录文件 及 移植详解
写在前面 2018/1/15更新了文章中的部分错误. FreeRTOS源码为最新版的10.0.1.FreeRTOS 10包含两个重要的新功能:流缓冲区和消息缓冲区. 从10.0.0开始,FreeRTO ...
- linux设备驱动开发详解源码,linux设备驱动开发详解光盘源码.rar
压缩包 : linux设备驱动开发详解光盘源码.rar 列表 19/busybox源代码/busybox-1.2.1.tar.bz2 19/MTD工具/mtd-utils-1.0.0.tar.gz 1 ...
- Android源码中的目录结构详解
由于自己从事与底层开发,一开始对项目结构不是很清楚,然后就百度找到了源码结构,我觉得讲的很清楚.Android 2.1源码结构 |-- Makefile |-- bionic ...
- Android进阶——Small源码分析之启动流程详解
前言 插件化现在已经是Android工程师必备的技能之一,只是学会怎么使用是不行的,所以蹭有时间研究一下Small的源码.对于插件化主要解决的问题是四大组件的加载和资源的加载,读懂所有Small源码需 ...
- Android源码之init.rc文件详解
一.引言 .rc文件是 android系统一个十分重要的文件. 其是资源文件,包括比如对话框.菜单.图标.字符串等资源信息. 使用.rc资源文件的目的是为了对程序中用到的大量的资源进行统一的管理. 本 ...
- Java API源码在哪里找_详解查看JAVA API及JAVA源码的方法
在java的日常学习中,我们有时候会需要看java的api说明,或者是查看java的源码,使我们更好的了解java,接下来我就来说说如何查看java的api以及java源码 对于java的api,一般 ...
- Android内核开发:源码的版本与分支详解
我想很多初学者或许跟我一样,看完Android源码下载相关的文章以后,就开始兴致勃勃地去下载Android源码了,但是下载完了源码后,有没有像我一样产生如下几个困惑呢? (1) Android版本有哪 ...
- STL源码剖析---红黑树原理详解下
转载请标明出处,原文地址:http://blog.csdn.net/hackbuteer1/article/details/7760584 算法导论书上给出的红黑树的性质如下,跟STL源码 ...
最新文章
- 把握每天的第一个钟头
- Google K8S与阿里Spring Cloud Alibaba相爱相杀,下一个神级架构来了!
- 图解系列之JVM运行时数据区
- python内存管理变量_Python变量内存管理
- cf1132E. Knapsack(搜索)
- Mellanox CX-5网卡支持OVS流表加速功能的调研
- CUDA TOOlkit Programming Guide K. Unified Memory Programming
- 分布式存储引擎OceanBase,UpdateServer 实现机制——存储引擎
- python 插入查找
- 数学建模(NO.10 典型相关分析)
- TikTok运营,TikTok数据分析
- 养生秘诀呼吸吐纳真的可以养生嘛?
- 使用 FireDAC的 TFDConnection, TFDQuery 最初只能显示50条记录,TDateSet.RecordCount总是获得50的解决方法。
- Android 项目必备(十四)--> 开发者选项
- 微信表情符号已写入判决书
- 怎么设置织梦栏目html结尾,dedecms网站栏目地址url优化成.html结尾的而不是文件夹形式结尾的。请大家来帮忙。...
- Android中通过来电转移实现“电话已关机”,“此号码已停机”等
- 无线Mesh网络技术基础与应用
- DVWA之Brute Force(暴力破解)
- 什么是元宇宙,它对未来意味着什么?