angularjs 源码解析之injector


Posted in Javascript onAugust 22, 2016

简介

injector是用来做参数自动注入的,例如

function fn ($http, $scope, aService) {
}

ng在运行时会把$http, $scope, aService 自动作为参数传入进行执行。

其实很容易想明白,injector做了两件事

  1. 缓存那些service,以后作为参数注入
  2. 分析参数列表,找到需要的参数注入

下面源码分析如何实现上面两件事情。

结构

createInjector -> createInternalInjector  return: instanceInjector

所以 createInjector() 返回的是 instanceInjector,结构如下:

{
 invoke: invoke,
 instantiate: instantiate,
 get: getService,
 annotate: annotate,
 has: function(name) {
  return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
 }
}

源码分析

1. createInjector

function createInjector(modulesToLoad, strictDi) {
 strictDi = (strictDi === true);
 var INSTANTIATING = {},
   providerSuffix = 'Provider',
   path = [],
   loadedModules = new HashMap([], true),
   // 预先配置$provide,供loadModules中调用注册service等
   providerCache = {
    $provide: {
      provider: supportObject(provider),
      factory: supportObject(factory),
      service: supportObject(service),
      value: supportObject(value),
      constant: supportObject(constant),
      decorator: decorator
     }
   },

   // providerInjector, instanceInjector 两个注入器
   // instanceInjector对外提供service等注入,providerInjector对内提供provider获取
   providerInjector = (providerCache.$injector =
     createInternalInjector(providerCache, function() {
      throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
     }, strictDi)),
   instanceCache = {},
   instanceInjector = (instanceCache.$injector =
     createInternalInjector(instanceCache, function(servicename) {
      var provider = providerInjector.get(servicename + providerSuffix);
      return instanceInjector.invoke(provider.$get, provider, undefined, servicename);
     }, strictDi));

 // 加载模块
 forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });

 return instanceInjector;
}

2. $provide

$provide: {
  provider: supportObject(provider),
  factory: supportObject(factory),
  service: supportObject(service),
  value: supportObject(value),
  constant: supportObject(constant),
  decorator: decorator
}

2.1 supportObject

用于包装方法,包装前的方法接受两个参数 (key, value),经过包装后的方法能支持传入object参数,即多个 key -> value。

function supportObject(delegate) {
 return function(key, value) {
  if (isObject(key)) {
   forEach(key, reverseParams(delegate));
  } else {
   return delegate(key, value);
  }
 };
}

2.2 provider

回顾下provider、service 和 factory的使用方式

app.factory('serviceName', function(){
 return {
  getName: function(){},
  setName: function(){}
 }
});

app.service('serviceName', function(){
 this.getName = function() {}

 this.setName = function() {}
});

app.provider('serviceName', function($httpProvider){
 // 注入$httpProvider
 this.$get = function() {
  return {
   getName: function(){},
   setName: function(){}
  };
 }
});

app.provider('serviceName', {
  $get: function () {}
});
function provider(name, provider_) {
 assertNotHasOwnProperty(name, 'service');
 // 当provider_是fn或者array时可以将其他provider注入到参数
 // 因为providerInjector.instantiate(provider_)时可以传入依赖的其他provider
 // 这也是provider与service,factory方法不一样的地方
 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)); }

最终汇总到provider的实现,将provider缓存到providerCache,供调用

跟其他不一样的就是constant的实现,分别保存到providerCache和instanceCache中,这样在定义provider还是在定义service是都能注入。

function constant(name, value) {
 assertNotHasOwnProperty(name, 'constant');
 providerCache[name] = value;
 instanceCache[name] = value;
}

2.3 回顾 loadModules

function runInvokeQueue(queue) {
 var i, ii;
 for(i = 0, ii = queue.length; i < ii; i++) {
  var invokeArgs = queue[i],
    provider = providerInjector.get(invokeArgs[0]);
  // 存入queue的如格式[$provide, factory, arguments]
  // 经过替换,$provide.factory.apply($provide, arguments);
  // 就是调用$provid的factory,service等
  provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
 }
}

2.4 decorator

示例:

module.config(function($provide) {
 $provide.decorator('Mail', function($delegate) {
  $delegate.addCC = function(cc) {
   this.cc.push(cc);
  };
  return $delegate;
 });
})

使用示例看出,传入的参数$delegate是原先的service实例,需要在该实例上添加方法都可以,即所谓的装饰器

源码:

function decorator(serviceName, decorFn) {
 var origProvider = providerInjector.get(serviceName + providerSuffix),
   orig$get = origProvider.$get;

 origProvider.$get = function() {
  // 通过上面获取的provider生成需要的service实例,再以$delegate注入到参数列表
  var origInstance = instanceInjector.invoke(orig$get, origProvider);
  return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
 };
}

3. createInternalInjector

3.1 整体结构

// 从cache中获取,没有的话调用factory进行创建,具体看getService解析

function createInternalInjector(cache, factory) {

 function getService(serviceName) {
 }

 function invoke(fn, self, locals, serviceName){
 }

 function instantiate(Type, locals, serviceName) {
 }

 return {
  // 执行fn,具有参数注入功能
  invoke: invoke,
  // 实例化fn, 可以参数注入
  instantiate: instantiate,
  // 获取provider或者service
  get: getService,
  // 获取方法的参数列表,供注入使用
  annotate: annotate,
  // 确认是否含有provider或service
  has: function(name) {
   return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
  }
 };
}

3.2 annotate

获取fn的参数列表

// type1
function fn (a, b, c) -> ['a', 'b', 'c']

// type2
['a', 'b', fn] -> ['a', 'b']

// type3
function fn () {}
fn.$inject = ['a', 'c']
-> ['a', 'c']

源码:

function annotate(fn, strictDi, name) {
 var $inject,
   fnText,
   argDecl,
   last;

 if (typeof fn === 'function') {
  if (!($inject = fn.$inject)) {
   $inject = [];
   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, '');
    // 将参数全部选出fn(a,b,c,d) -> 'a,b,c,d'
    argDecl = fnText.match(FN_ARGS);
    // 分割成array
    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;
}

3.3 getService

// 当cache中没有该service时,进入else, 先cache[serviceName] = INSTANTIATING 做一个标记
// 因为接下来调用factory(serviceName),其实是一个递归调用
// function(servicename) {
//  var provider = providerInjector.get(servicename + providerSuffix);
//  return instanceInjector.invoke(provider.$get, provider, undefined, servicename);
// }
// instanceInjector.invoke(provider.$get 时会将需要注入的参数get出来然后注入
// 因此做上标记后就可以判断是否有循环依赖
function getService(serviceName) {
 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;
   return cache[serviceName] = factory(serviceName);
  } catch (err) {
   if (cache[serviceName] === INSTANTIATING) {
    delete cache[serviceName];
   }
   throw err;
  } finally {
   path.shift();
  }
 }
}

3.4 invoke

function invoke(fn, self, locals, serviceName){
 if (typeof locals === 'string') {
  serviceName = locals;
  locals = null;
 }

 var args = [],
   // 获取参数列表
   $inject = 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);
  }
  // locals优先
  args.push(
   locals && locals.hasOwnProperty(key)
   ? locals[key]
   : getService(key)
  );
 }
 if (isArray(fn)) {
  fn = fn[length];
 }

 return fn.apply(self, args);
}

3.5 instantiate

function instantiate(Type, locals, serviceName) {
 var Constructor = function() {},
   instance, returnedValue;
 
 // 当type为array时,获取最后的参数如:['$window', function($win){}]
 Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype;
 instance = new Constructor();
 // 调用invoke执行Type方法
 returnedValue = invoke(Type, instance, locals, serviceName);

 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
}

instantiate 的作用是用来实例化Type的,在实例化的过程中可以自动传入参数到构造函数。

Javascript 相关文章推荐
Prototype Date对象 学习
Jul 12 Javascript
过虑特殊字符输入的js代码
Aug 05 Javascript
固定表格行列(expression)在IE下适用
Jul 25 Javascript
JavaScript中创建类/对象的几种方法总结
Nov 29 Javascript
javascript与有限状态机详解
May 08 Javascript
jQuery实现感应鼠标动画效果自动伸长的输入框实例
Feb 24 Javascript
javascript中eval解析JSON字符串
Feb 27 Javascript
AngularJs表单验证实例代码解析
Nov 29 Javascript
Bootstrap和Java分页实例第二篇
Dec 23 Javascript
js中作用域的实例解析
Mar 16 Javascript
Node.js 异步异常的处理与domain模块解析
May 10 Javascript
ES6 let和const定义变量与常量的应用实例分析
Jun 27 Javascript
基于jQuery实现表格内容的筛选功能
Aug 21 #Javascript
jQuery Easyui快速入门教程
Aug 21 #Javascript
原生JS实现轮播效果+学前端的感受(防止走火入魔)
Aug 21 #Javascript
Javascript发送AJAX请求实例代码
Aug 21 #Javascript
原生js实现jquery函数animate()动画效果的简单实例
Aug 21 #Javascript
原生JS实现图片轮播与淡入效果的简单实例
Aug 21 #Javascript
JavaScript实战(原生range和自定义特效)简单实例
Aug 21 #Javascript
You might like
php中的时间显示
2007/01/18 PHP
PHP 数组和字符串互相转换实现方法
2013/03/26 PHP
php使用curl出现Expect:100-continue解决方法
2015/03/03 PHP
PHP计算日期相差天数实例分析
2016/02/23 PHP
完美解决phpdoc导出文档中@package的warning及Error的错误
2016/05/17 PHP
AJAX的使用方法详解
2017/04/29 PHP
JS类中定义原型方法的两种实现的区别
2007/03/08 Javascript
javascript学习笔记(十七) 检测浏览器插件代码
2012/06/20 Javascript
jquery 如何动态添加、删除class样式方法介绍
2012/11/07 Javascript
js动态为代码着色显示行号
2013/05/29 Javascript
document.documentElement和document.body区别介绍
2013/09/16 Javascript
js控制table合并具体实现
2014/02/20 Javascript
js实现动画特效的文字链接鼠标悬停提示的方法
2015/03/02 Javascript
基于jQuery创建鼠标悬停效果的方法
2015/03/07 Javascript
Javascript中prototype的使用详解
2016/06/18 Javascript
如何清除IE10+ input X 文本框的叉叉和密码输入框的眼睛图标
2016/12/21 Javascript
使用JavaScript实现一个小程序之99乘法表
2017/09/21 Javascript
webpack打包node.js后端项目的方法
2018/03/10 Javascript
Angular6中使用Swiper的方法示例
2018/07/09 Javascript
详解javascript 变量提升(Hoisting)
2019/03/12 Javascript
Angular6项目打包优化的实现方法
2019/12/15 Javascript
vue中根据时间戳判断对应的时间(今天 昨天 前天)
2019/12/20 Javascript
微信小程序实现首页弹出广告
2020/12/03 Javascript
[04:54]DOTA2 2017国际邀请赛:上届冠军WINGS采访短片
2017/08/09 DOTA
python3.6+django2.0开发一套学员管理系统
2018/03/03 Python
Python 监测文件是否更新的方法
2019/06/10 Python
Django使用Channels实现WebSocket的方法
2019/07/28 Python
使用python-pptx包批量修改ppt格式的实现
2020/02/14 Python
python 从list中随机取值的方法
2020/11/16 Python
硕士研究生自我鉴定范文
2013/12/27 职场文书
反邪教宣传工作方案
2014/05/07 职场文书
民生工作实施方案
2014/05/31 职场文书
单位委托书怎么写
2014/08/02 职场文书
中学教师读书笔记
2015/07/01 职场文书
清洁工工作总结
2015/08/11 职场文书
科普 | 业余无线电知识-波段篇
2022/02/18 无线电