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 相关文章推荐
Javascript之文件操作
Mar 07 Javascript
javascript创建数组之联合数组的使用方法示例
Dec 26 Javascript
js实现滑动触屏事件监听的方法
May 05 Javascript
浅谈javascript的分号的使用
May 12 Javascript
在JavaScript中操作时间之getMonth()方法的使用
Jun 10 Javascript
jQuery实现批量判断表单中文本框非空的方法(2种方法)
Dec 09 Javascript
关于网页中的无缝滚动的js代码
Jun 09 Javascript
详解Javascript数据类型的转换规则
Dec 12 Javascript
详解vue2路由vue-router配置(懒加载)
Apr 08 Javascript
老生常谈Bootstrap媒体对象
Jul 06 Javascript
微信小程序实现页面跳转传值以及获取值的方法分析
Dec 18 Javascript
优雅的将ElementUI表格变身成树形表格的方法步骤
Apr 11 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不允许用户提交空表单(php空值判断)
2013/11/12 PHP
PHP实现的汉字拼音转换和公历农历转换类及使用示例
2014/07/01 PHP
PHP 裁剪图片
2021/03/09 PHP
jQuery 三击事件实现代码
2013/09/11 Javascript
javascript中的undefined和not defined区别示例介绍
2014/02/26 Javascript
一段非常简单的js判断浏览器的内核
2014/08/17 Javascript
使用纯javascript实现放大镜效果
2015/03/18 Javascript
JavaScript面对国际化编程时的一些建议
2015/06/24 Javascript
javascript+ajax实现产品页面加载信息
2015/07/09 Javascript
JavaScript生成验证码并实现验证功能
2016/09/24 Javascript
微信小程序 教程之数据绑定
2016/10/18 Javascript
JQuery Ajax WebService传递参数的简单实例
2016/11/02 Javascript
微信小程序 视图容器组件的详解及实例代码
2017/01/19 Javascript
详解AngularJs ui-router 路由的简单介绍
2017/04/26 Javascript
AngularJS使用ng-class动态增减class样式的方法示例
2017/05/18 Javascript
javascript实现遮罩层动态效果实例
2019/05/14 Javascript
js验证账户名是否重复
2020/05/26 Javascript
vue实现抽屉弹窗效果
2020/11/15 Javascript
python微信跳一跳系列之自动计算跳一跳距离
2018/02/26 Python
利用Python将每日一句定时推送至微信的实现方法
2018/08/13 Python
python 利用for循环 保存多个图像或者文件的实例
2018/11/09 Python
Python实现堡垒机模式下远程命令执行操作示例
2019/05/09 Python
Django实现whoosh搜索引擎使用jieba分词
2020/04/08 Python
Python实现EM算法实例代码
2020/10/04 Python
Python 图片处理库exifread详解
2021/02/25 Python
HTML5实现动画效果的方式汇总
2016/02/29 HTML / CSS
New Balance加拿大官方网站:运动鞋和健身服装
2018/11/19 全球购物
Otticanet意大利:最顶尖的世界名牌眼镜, 能得到打折季的价格
2019/03/10 全球购物
TOWER London官网:鞋子、靴子、运动鞋等
2019/07/14 全球购物
体育专业学生自我评价范文
2014/01/17 职场文书
服务标语大全
2014/06/18 职场文书
医院深入开展党的群众路线教育实践活动实施方案
2014/08/27 职场文书
弄虚作假心得体会
2014/09/10 职场文书
PHP解决高并发问题
2021/04/01 PHP
python基础之while循环语句的使用
2021/04/20 Python
使用RedisTemplat实现简单的分布式锁
2021/11/20 Redis