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 相关文章推荐
关于文本限制字数的js代码
Apr 02 Javascript
javascript 函数调用的对象和方法
Jul 01 Javascript
JavaScript判断变量是否为undefined的两种写法区别
Dec 04 Javascript
JavaScript也谈内存优化
Jun 06 Javascript
javascript中sort() 方法使用详解
Aug 30 Javascript
初识简单却不失优雅的Vue.js
Sep 12 Javascript
AngularJS的依赖注入实例分析(使用module和injector)
Jan 19 Javascript
jQuery实现基本淡入淡出效果的方法详解
Sep 05 jQuery
Vue表单绑定的实例代码(单选按钮,选择框(单选时,多选时,用 v-for 渲染的动态选项)
May 13 Javascript
Vue+ElementUI项目使用webpack输出MPA的方法
Aug 27 Javascript
使用Bootstrap做一个朝代历史表
Dec 10 Javascript
Node.js+Vue脚手架环境搭建的方法步骤
Mar 08 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极大的增强功能和性能
2006/10/09 PHP
基于qmail的完整WEBMAIL解决方案安装详解
2006/10/09 PHP
php 动态添加记录
2009/03/10 PHP
php学习笔记之 函数声明(二)
2011/06/09 PHP
php引用传值实例详解学习
2013/11/06 PHP
php获取指定范围内最接近数的方法
2015/06/02 PHP
php支付宝系列之电脑网站支付
2018/05/30 PHP
php PDO属性设置与操作方法分析
2018/12/27 PHP
php 使用html5 XHR2实现上传文件与进度显示功能示例
2020/03/03 PHP
通用JS事件写法实现代码
2009/01/07 Javascript
理解JavaScript的caller,callee,call,apply
2009/04/28 Javascript
javascript 回调函数详解
2014/11/11 Javascript
javascript中slice(),splice(),split(),substring(),substr()使用方法
2015/03/13 Javascript
JavaScript获取数组最小值和最大值的方法
2015/06/09 Javascript
针对初学者的jQuery入门指南
2015/08/15 Javascript
学习使用bootstrap基本控件(table、form、button)
2016/04/12 Javascript
浅谈JS之tagNaem和nodeName
2016/09/13 Javascript
尝试自己动手用react来写一个分页组件(小结)
2018/02/09 Javascript
vue首次赋值不触发watch的解决方法
2018/09/11 Javascript
webpack4 配置 ssr 环境遇到“document is not defined”
2019/10/24 Javascript
vue2.0 解决抽取公用js的问题
2020/07/31 Javascript
UEditor 自定义图片视频尺寸校验功能的实现代码
2020/10/20 Javascript
[02:19]2014DOTA2国际邀请赛 专访820少年们一起去追梦吧
2014/07/14 DOTA
Python 实现一个颜色色值转换的小工具
2016/12/06 Python
使用 Python 处理3万多条数据只要几秒钟
2020/01/19 Python
终于搞懂了Keras中multiloss的对应关系介绍
2020/06/22 Python
python中用ctypes模拟点击的实例讲解
2020/11/26 Python
SmartBuyGlasses荷兰:购买太阳镜和眼镜
2020/03/16 全球购物
给老婆大人的检讨书
2014/02/24 职场文书
软件售后服务承诺书
2014/05/21 职场文书
财务个人年度总结范文
2015/02/26 职场文书
2015年大学班长个人工作总结
2015/04/24 职场文书
Spring Security使用单点登录的权限功能
2022/04/03 Java/Android
《最终幻想14》6.01版本4月5日推出 追加新任务新道具
2022/04/03 其他游戏
SpringBoot项目多数据源及mybatis 驼峰失效的问题解决方法
2022/07/07 Java/Android
TypeScript实用技巧 Nominal Typing名义类型详解
2022/09/23 Javascript