昨天晚上写完angular源码分析:angular中jqLite的实现——你可以丢掉jQuery了,给今天定了一个题angular源码分析:injector.js文件,以及angular的加载流程,但是想了想,加载流程还是放到后面统一再讲比较好。

如果你没有看过笔者的angular源码分析:angular中的依赖注入式如何实现的,可以点击看看,在其中讲过的内容,我将不会再这里重复,这一期将作那一期的补充。

一、从createInjector函数开始

先省去具体实现,总体看看:函数拥有两个参数,modulesToLoad, strictDi,从单词命名上来看,第一个参数是要没加载的模块,第二参数是严格的依赖注入;另外函数对象本身绑定了一个annotate,在之前我们讲过annotate是一个可以将函数中的参数提出来的函数。

  1. function createInjector(modulesToLoad, strictDi) { ...}
  2. createInjector.$$annotate = annotate;

二、理解createInjector函数的实现

先上代码:

  1. strictDi = (strictDi === true);
  2. var INSTANTIATING = {},
  3. providerSuffix = 'Provider',
  4. path = [],
  5. loadedModules = new HashMap([], true),
  6. providerCache = { //用存放provider的cache
  7. $provide: {
  8. provider: supportObject(provider),
  9. factory: supportObject(factory),
  10. service: supportObject(service),
  11. value: supportObject(value),
  12. constant: supportObject(constant),
  13. decorator: decorator
  14. }
  15. },
  16. providerInjector = (providerCache.$injector =
  17. createInternalInjector(providerCache, function(serviceName, caller) { //调用createInternalInjector生成内部的注入器
  18. if (angular.isString(caller)) {
  19. path.push(caller);
  20. }
  21. throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
  22. })),
  23. instanceCache = {}, //用来存放服务实例的cache
  24. instanceInjector = (instanceCache.$injector =
  25. createInternalInjector(instanceCache, function(serviceName, caller) {
  26. var provider = providerInjector.get(serviceName + providerSuffix, caller);
  27. return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
  28. }));
  29. forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });
  30. return instanceInjector;

1.这个函数做了什么。

基本上是定义一堆东西,其中最重要的是providerCache和providerInjector 以及instanceCache和instanceInjector。关于createInternalInjector这个函数,在angular源码分析:angular中的依赖注入式如何实现的中讲过,主要功能是利用提供的cache(第一个参数u)和factory(第二参数),构造一个内部注入器,其本身也是工厂函数。

2.providerCache 和$provide

我们可以这样理解,providerCache中存放的就是各种服务的提供者的实例。比如定义一个服务,叫"dapeng",那么提供者就是"dapengProvider"。而这个容器(providerCache)在初始化时,就默认放入了一个$provide。

  1. providerCache = {
  2. $provide: {
  3. provider: supportObject(provider),
  4. factory: supportObject(factory),
  5. service: supportObject(service),
  6. value: supportObject(value),
  7. constant: supportObject(constant),
  8. decorator: decorator
  9. }
  10. },

很眼熟吧,这是不是可以用于定义服务的几种方式呢?

3.forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });中的loadModules函数:

  1. ////////////////////////////////////
  2. // Module Loading
  3. ////////////////////////////////////
  4. function loadModules(modulesToLoad) {
  5. assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array');
  6. var runBlocks = [], moduleFn;
  7. forEach(modulesToLoad, function(module) {
  8. if (loadedModules.get(module)) return;
  9. loadedModules.put(module, true);
  10. function runInvokeQueue(queue) { //执行调用队列
  11. var i, ii;
  12. for (i = 0, ii = queue.length; i < ii; i++) {
  13. var invokeArgs = queue[i],
  14. provider = providerInjector.get(invokeArgs[0]);
  15. provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
  16. }
  17. }
  18. try {
  19. if (isString(module)) {
  20. moduleFn = angularModule(module); //加载模块
  21. runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); //递归加载依赖模块,获取所有模块的run函数定义的代码。
  22. runInvokeQueue(moduleFn._invokeQueue); //moduleFn._invokeQueue是什么鬼,先留坑在此
  23. runInvokeQueue(moduleFn._configBlocks); //moduleFn._configBlocks是什么鬼,也留坑在此
  24. } else if (isFunction(module)) {
  25. runBlocks.push(providerInjector.invoke(module));
  26. } else if (isArray(module)) {
  27. runBlocks.push(providerInjector.invoke(module));
  28. } else {
  29. assertArgFn(module, 'module');
  30. }
  31. } catch (e) {
  32. if (isArray(module)) {
  33. module = module[module.length - 1];
  34. }
  35. if (e.message && e.stack && e.stack.indexOf(e.message) == -1) {
  36. // Safari & FF's stack traces don't contain error.message content
  37. // unlike those of Chrome and IE
  38. // So if stack doesn't contain message, we create a new string that contains both.
  39. // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
  40. /* jshint -W022 */
  41. e = e.message + '\n' + e.stack;
  42. }
  43. throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}",
  44. module, e.stack || e.message || e);
  45. }
  46. });
  47. return runBlocks;
  48. }

angularModule是什么鬼,留坑先不讲。可以先理解为,用于加载一个module。

runBlocks将得到一个数组,这个数组的元素是一些函数,这些函数是定义模块后通过run(function(){})注册的函数。

runInvokeQueue函数,将执行调用队列,可以从这个函数的实现上来看,参数queue应该是一个二维数组。[['name','index',params]],这个函数将循环处理queue数组。

** moduleFn._invokeQueue 和 moduleFn._configBlocks** 本期先不讲,留坑在此,等讲“加载流程”再讲。

那么,这个函数最红就是返回的一个函数数组:runBlocks。

可以基本推出:forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });是执行所有的模块中run的代码,而在run的代码执行前,先执行了服务的定义代码和模块config代码。

三、$provider

还是先上代码:

  1. ////////////////////////////////////
  2. // $provider
  3. ////////////////////////////////////
  4. function supportObject(delegate) {
  5. return function(key, value) {
  6. if (isObject(key)) { //如果key是一个对象
  7. forEach(key, reverseParams(delegate));
  8. } else {
  9. return delegate(key, value);
  10. }
  11. };
  12. }
  13. function provider(name, provider_) {
  14. assertNotHasOwnProperty(name, 'service');
  15. if (isFunction(provider_) || isArray(provider_)) {
  16. provider_ = providerInjector.instantiate(provider_);
  17. }
  18. if (!provider_.$get) {
  19. throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name);
  20. }
  21. return providerCache[name + providerSuffix] = provider_;
  22. }
  23. function enforceReturnValue(name, factory) {
  24. return function enforcedReturnValue() {
  25. var result = instanceInjector.invoke(factory, this);
  26. if (isUndefined(result)) {
  27. throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
  28. }
  29. return result;
  30. };
  31. }
  32. function factory(name, factoryFn, enforce) {
  33. return provider(name, {
  34. $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
  35. });
  36. }
  37. function service(name, constructor) {
  38. return factory(name, ['$injector', function($injector) {
  39. return $injector.instantiate(constructor);
  40. }]);
  41. }
  42. function value(name, val) { return factory(name, valueFn(val), false); }
  43. function constant(name, value) {
  44. assertNotHasOwnProperty(name, 'constant');
  45. providerCache[name] = value;
  46. instanceCache[name] = value;
  47. }
  48. function decorator(serviceName, decorFn) {
  49. var origProvider = providerInjector.get(serviceName + providerSuffix),
  50. orig$get = origProvider.$get;
  51. origProvider.$get = function() {
  52. var origInstance = instanceInjector.invoke(orig$get, origProvider);
  53. return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
  54. };
  55. }

1.supportObject函数

这里先引用一下reverseParams的实现

  1. function reverseParams(iteratorFn) {
  2. return function(value, key) { iteratorFn(key, value); };
  3. }

来看看下面的代码将会得到什么,就知道这个函数在做什么了:

  1. function agent(key,vlue){
  2. console.log(key + '--->'+ value);
  3. }
  4. var new_agent = supportObject(agent);
  5. new_agent({a:123,b:456,c:'abc'});
  6. new_agent('key','value');

2.provider函数,参数name,provider_

作用,创建服务的提供者,serviceProvider,并且用providerCache保存起来。

3.enforceReturnValue

直接调用instanceInjector.invoke来生成服务。

4.factory

调用provide函数,由函数自身提供一个Provider。

5.service,继续简化服务的定义。

这意味者,可以给service的第二参数传递一个构成函数,service会利用构造函数“new”出一个服务对象来。如果你已经有一个构造函数,需要定义这个构造函数生成的对象为服务,可以考虑使用这个方法。

6.value,继续简化服务的定义。

第二个参数,是一个对象(可以是一个基础类型的值)或者是一个可以返回对象的函数。如果你定义的服务是一个对象,可以考虑用这个方法。

7.constant

可以看到providerCache和instanceCache中的存储用的键是一个,就是说,通过这个函数定义的服务,可以在模块的config阶段和run阶段同时有效。

8.decorator,装饰模式的实现

通过代码分析,可以得到结论:decorator可以对一个已有的服务进行重新装饰。

举例

  1. decorator('exist_service',function($delegate){
  2. $delegate.add_method = function(){
  3. console.log('this method is added');
  4. };
  5. });

上面的代码,将会向服务“exist_service”增加一个add_method方法。

在最新的angular版本中,已经可以采用angular.module('xxx').decorator('exist_service',function($delegate){})的方式来使用decorator,我们讲的这个版本,还只能在config中使用。

上一期:angular源码分析:angular中jqLite的实现——你可以丢掉jQuery了

下一期:angular源码分析:angular的整个加载流程

angular源码分析:injector.js文件分析——angular中的依赖注入式如何实现的(续)的更多相关文章

  1. zepto源码研究 - ajax.js(请求过程中的各个事件分析)

    简要:ajax请求具有能够触发各类事件的功能,包括:触发全局事件,请求发送前事件,请求开始事件,请求结束事件等等,贯穿整个ajax请求过程,这是非常有用的,我们可以利用这些事件来做一些非常有意思的事情 ...

  2. angular源码分析:angular的整个加载流程

    在前面,我们讲了angular的目录结构.JQLite以及依赖注入的实现,在这一期中我们将重点分析angular的整个框架的加载流程. 一.从源代码的编译顺序开始 下面是我们在目录结构哪一期理出的an ...

  3. angular源码分析:angular中jqLite的实现——你可以丢掉jQuery了

    一.从function JQLite(element)函数开始. function JQLite(element) { if (element instanceof JQLite) { //情况1 r ...

  4. angular源码分析:angular的源代码目录结构说明

    一.读源码,是选择"编译合并后"的呢还是"编译前的"呢? 有朋友说,读angular源码,直接看编译后的,多好,不用管模块间的关系,从上往下读就好了.但是在我看 ...

  5. angular源码分析:angular中各种常用函数,比较省代码的各种小技巧

    angular的工具函数 在angular的API文档中,在最前面就是讲的就是angular的工具函数,下面列出来 angular.bind //用户将函数和对象绑定在一起,返回一个新的函数 angu ...

  6. angular源码分析:angular中入境检察官$sce

    一.ng-bing-html指令问题 需求:我需要将一个变量$scope.x = '<a href="http://www.cnblogs.com/web2-developer/&qu ...

  7. angular源码分析:angular中脏活累活承担者之$parse

    我们在上一期中讲 $rootscope时,看到$rootscope是依赖$prase,其实不止是$rootscope,翻看angular的源码随便翻翻就可以发现很多地方是依赖于$parse的.而$pa ...

  8. angular源码分析:angular中$rootscope的实现——scope的一生

    在angular中,$scope是一个关键的服务,可以被注入到controller中,注入其他服务却只能是$rootscope.scope是一个概念,是一个类,而$rootscope和被注入到cont ...

  9. angular源码分析:angular中的依赖注入式如何实现的

    一.准备 angular的源码一份,我这里使用的是v1.4.7.源码的获取,请参考我另一篇博文:angular源码分析:angular源代码的获取与编译环境安装 二.什么是依赖注入 据我所知,依赖注入 ...

随机推荐

  1. [Node.js] 对称加密、公钥加密和RSA

    原文地址:http://www.moye.me/2015/06/14/cryptography_rsa/ 引子 对于加解密,我一直处于一种知其然不知其所以然的状态,项目核心部分并不倚重加解密算法时,可 ...

  2. Anliven - 如何逼疯你的小伙伴

    如果你也曾为某人某事"发疯发狂,懵逼连连". . 无礼:随意牵扯他人,不了解实际情况,却对他人工作横加点评甚至是指责. 无知:自我感觉良好,自己总是最正确最合理的,除了自己,没人会 ...

  3. iOS_UIImage_给图片添加水印

    github地址: https://github.com/mancongiOS/UIImage.git UIImage的Category UIImage+ImageWaterPrint.h #impo ...

  4. ASP.NET 程序提交表单数据中带有html标签不能提交或者提交报错问题

    今天在公司做另外的一个项目,又奇葩的遇到一个问题. 在本地自己电脑上怎么测试都是正常的.但是先上服务器就出问题: 用富文本编辑器上传一篇文章,始终报错,又没提示具体什么错误,也没说代码错误,点击提交按 ...

  5. CentOS7 Nexus安装

    CentOS7 Nexus安装 CentOS7 Nexus安装 Download 从Nexus下载nexus-2.11.2-03-bundle.tar.gz Install 安装 上传RPM文件到/t ...

  6. Git:错误:error:src refspec master does not match any

    新建立了一个远程仓库,想着把项目放上去.于是在项目目录上: git init 然后就添加远程库 git remote add origin xxxx.git 然后就想push: git push -u ...

  7. .NET Core爬坑记 1.0 项目文件

    前言: 之所以要写这个系列是因为在移植项目到ASP.NET Core平台的过程中,遇到了一些“新变化”,这些变化有编译方面的.有API方面的,今天要讲的是编译方面的一些问题.我把它们整理后分享出来,以 ...

  8. 交换排序---快速排序算法(Javascript版)

    快速排序是对冒泡排序的一种改进.通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行 ...

  9. c# TCP Socket通讯基础

    在做网络通讯方面的程序时,必不可少的是Socket通讯. 那么我们需要有一套既定的,简易的通讯流程. 如下: <pre name="code" class="csh ...

  10. ASP.NET Core开发-MVC 使用dotnet 命令创建Controller和View

    使用dotnet 命令在ASP.NET Core MVC 中创建Controller和View,之前讲解过使用yo 来创建Controller和View. 下面来了解dotnet 命令来创建Contr ...