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 相关文章推荐
无阻塞加载脚本分析[全]
Jan 20 Javascript
由点击页面其它地方隐藏div所想到的jQuery的delegate
Aug 29 Javascript
Jquery实现的一种常用高亮效果示例代码
Jan 28 Javascript
js实现左侧网页tab滑动门效果代码
Sep 06 Javascript
javascript给span标签赋值的方法
Nov 26 Javascript
利用JavaScript阻止表单提交的两种方法
Aug 11 Javascript
面试常见的js算法题
Mar 23 Javascript
JavaScript常用数组操作方法,包含ES6方法
May 10 Javascript
iview Upload组件多个文件上传的示例代码
Sep 30 Javascript
灵活使用console让js调试更简单的方法步骤
Apr 23 Javascript
原生JavaScript写出Tabs标签页的实例代码
Jul 20 Javascript
vue-以文件流-blob-的形式-下载-导出文件操作
Aug 07 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
与数据库连接
2006/10/09 PHP
供参考的 php 学习提高路线分享
2011/10/23 PHP
Codeigniter整合Tank Auth权限类库详解
2014/06/12 PHP
ThinkPHP让分页保持搜索状态的方法
2014/07/02 PHP
推荐25款php中非常有用的类库
2014/09/29 PHP
smarty内置函数config_load用法实例
2015/01/22 PHP
PHP输出Excel PHPExcel的方法
2018/07/26 PHP
php从数据库中获取数据用ajax传送到前台的方法
2018/08/20 PHP
使用PHP开发留言板功能
2019/11/19 PHP
通过JS自动隐藏手机浏览器的地址栏实现原理与代码
2013/01/02 Javascript
文本框倒叙输入让输入框的焦点始终在最开始的位置
2014/09/01 Javascript
JQuery设置时间段下拉选择实例
2014/12/30 Javascript
被遗忘的javascript的slice() 方法
2015/04/20 Javascript
JavaScript中Number.NEGATIVE_INFINITY值的使用详解
2015/06/05 Javascript
Bootstrap中文本框的宽度变窄并且加入一副验证码图片的实现方法
2016/06/23 Javascript
JS实现重新加载当前页面
2016/11/29 Javascript
微信小程序新手教程之启动页的重要性
2019/03/03 Javascript
浅谈vue项目用到的mock数据接口的两种方式
2019/10/09 Javascript
python根据出生年份简单计算生肖的方法
2015/03/27 Python
python压缩文件夹内所有文件为zip文件的方法
2015/06/20 Python
Python3 伪装浏览器的方法示例
2017/11/23 Python
Python元字符的用法实例解析
2018/01/17 Python
Python 3.3实现计算两个日期间隔秒数/天数的方法示例
2019/01/07 Python
python根据完整路径获得盘名/路径名/文件名/文件扩展名的方法
2020/04/22 Python
python 爬虫网页登陆的简单实现
2020/11/30 Python
Europcar意大利:汽车租赁
2019/07/07 全球购物
应届生财务管理求职信
2013/11/06 职场文书
工作表现评语
2014/01/19 职场文书
最新个人职业生涯规划书
2014/01/22 职场文书
扶贫办主任查摆“四风”问题个人对照检查材料思想汇报
2014/10/02 职场文书
群众路线教育查摆剖析材料
2014/10/10 职场文书
机关作风建设自查报告及整改措施
2014/10/21 职场文书
公司员工辞职信范文
2015/05/12 职场文书
聘任书格式及范文
2015/09/21 职场文书
SQLServer2019 数据库环境搭建与使用的实现
2021/04/08 SQL Server
MySQL创建定时任务
2022/01/22 MySQL