Angular.JS学习之依赖注入$injector详析

前言

在依赖注入(IoC)之前,我们在程序中需要创建一个对象很简单也很直接,就是在代码中new Object即可,有我们自己负责创建、维护、修改和删除,也就是说,我们控制了对象的整个生命周期,直到对象没有被引用,被回收。诚然,当创建或者维护的对象数量较少时,这种做法无可厚非,但是当一个大项目中需要创建大数量级的对象时,仅仅依靠程序员来进行维护所有对象,这是难以做到的,特别是如果想在程序的整个生命周期内复用一些对象,我们需要自己写一个缓存模块对所有对象进行缓存,这加大了复杂度。当然,IoC的好处并不仅限于此,它也降低了对依赖的耦合度,不必在代码中进行引用或者传参即可操作依赖。

在js中,我们可以这样引入依赖

1、使用全局变量引用

2、在需要的地方通过函数参数传递

使用全局变量的坏处自不必说,污染了全局的名字空间,而通过函参传递引用,也可以通过两种方法实现:

1、闭包传递

2、后台解析出依赖对象,并通过Function.prototype.call进行传参

而在AngularJS中,依赖注入是通过后者实现的,接下来的几节将会介绍IoC模块的具体实现。

获取依赖

var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
 // 获取服务名
var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var $injectorMinErr = minErr('$injector');

function anonFn(fn) {
 // For anonymous functions, showing at the very least the function signature can help in
 // debugging.
 var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
  args = fnText.match(FN_ARGS);
 if (args) {
 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
 }
 return 'fn';
}

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, '');
  argDecl = fnText.match(FN_ARGS);
  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;
}

annotate函数通过对入参进行针对性分析,若传递的是一个函数,则依赖模块作为入参传递,此时可通过序列化函数进行正则匹配,获取依赖模块的名称并存入$inject数组中返回,另外,通过函数入参传递依赖的方式在严格模式下执行会抛出异常;第二种依赖传递则是通过数组的方式,数组的最后一个元素是需要使用依赖的函数。annotate函数最终返回解析的依赖名称。

注入器的创建

AngularJS的API也提供了injector部分,通过injector部分,通过injector可以使用get,has,instantiate,invoke以及上节提到的annotate等方法,通过源码可以更清晰的理解。

function createInternalInjector(cache, factory) {
 // 对服务注入器 providerInjector而言,只根据服务名获取服务,factory会抛出异常
 function getService(serviceName, caller) {
  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, caller);
  } catch (err) {
   if (cache[serviceName] === INSTANTIATING) {
   delete cache[serviceName];
   }
   throw err;
  } finally {
   path.shift();
  }
  }
 }

 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);
  }
  // 注入的服务作为参数传入
  args.push(
   locals && locals.hasOwnProperty(key)
   ? locals[key]
   : getService(key, serviceName)
  );
  }
  if (isArray(fn)) {
  fn = fn[length];
  }

  // http://jsperf.com/angularjs-invoke-apply-vs-switch
  // #5388
  return fn.apply(self, args);
 }

 function instantiate(Type, locals, serviceName) {
  // Check if Type is annotated and use just the given function at n-1 as parameter
  // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
  // Object creation: http://jsperf.com/create-constructor/2
  var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype);
  var returnedValue = invoke(Type, instance, locals, serviceName);

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

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

createInternalInjector方法创建$injector对象,传递的参数分别为缓存对象和工厂函数。在具体实现中,AngularJS创建了两个injector对象--providerInjector和instanceInjector(这两个对象的不同主要是createInternalInjector方法传递的缓存对象不同),而通过angular.injector()导出的就是instanceInjector。对于providerInjector,主要用来获取服务的提供者,即serviceProvider。而对于instanceInjector而言,主要用于执行从providerInjector获取的provider对象的$get方法,生产服务对象(依赖),并将服务对象传递给相应的函数,完成IoC。

首先从get方法说起,get方法主要获取指定名称的服务,通过angular的injector方法获取的是instanceInjector,而当缓存中没有该服务对象(依赖)时,我们需要执行factory(serviceName, caller)方法,我们看看对于的factory函数:

instanceInjector = (instanceCache.$injector =
   createInternalInjector(instanceCache, function
(serviceName, caller) { var provider = providerInjector.get(serviceName + providerSuffix, caller); return
 instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
   }));

红色部分即为factory函数,它显示通过providerInjector获取相应服务的提供者serviceProvider,然后调用instanceInjector的invoke方法在serviceProvider上下文执行serviceProvider的$get方法,返回服务对象并保存在缓存中。这样,便完成了服务对象(依赖)的获取和缓存。

invoke方法也很简单,它的入参分别问fn, self, locals, serviceName,即要执行的函数,函数执行的上下文,提供的options选项和服务名。首先获取函数的所有依赖名,通过annotate方法完成之后,如果options中提供了对于名称的依赖,则使用,否则通过get方法获取依赖,最后传入函数,并将函数的执行结果返回。invoke返回的结果往往是一个服务对象。

instantiate方法主要根据提供的构造函数创建一个示例,用作依赖或提供服务。值得一提的是并没有通过new关键字创建对象,而是通过ECMA5提供的Object.create来继承函数的原型对象实现,非常巧妙。

has方法则是相继判断serviceProvider和service是否存在于缓存中。

至此,$injector对象创建完毕。

注册服务(依赖)

服务不可能凭空而来,我们需要自己实现或者外部引入服务或依赖。所以,注册服务的模块也是值得深究的。AngularJS提供了多种注册服务的API,但是我们着重关注的是provider方法,其他factory,service方法都是基于此进行构建的。

这些方法(provider,factory等)绑定在providerCache.provide对象上,而我们通过angular.module(′app′,[]).provider(...)方式调用的provider函数,会在module加载期间将调用(该调用抽象成一个数组,即[provider,method,arguments])绑定在内部的invokeQueue数组中,最终在providerCache.provide对象上,而我们通过angular.module(′app′,[]).provider(...)方式调用的provider函数,会在module加载期间将调用(该调用抽象成一个数组,即[provider,method,arguments])绑定在内部的invokeQueue数组中,最终在providerCache.provide对象上调用provider方法,其他的controller,directive等方法类似,不过是绑定在providerCache.controllerProvider,providerCache.controllerProvider,providerCache.compileProvider对象上。

function provider(name, provider_) {
 assertNotHasOwnProperty(name, 'service');
 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 enforceReturnValue(name, factory) {
 return function enforcedReturnValue() {
  var result = instanceInjector.invoke(factory, this);
  if (isUndefined(result)) {
  throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
  }
  return result;
 };
 }

 function factory(name, factoryFn, enforce) {
 return provider(name, {
  $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
 });
 }

 function service(name, constructor) {
 return factory(name, ['$injector', function($injector) {
  return $injector.instantiate(constructor);
 }]);
 }

 function value(name, val) { return factory(name, valueFn(val), false); }

 function constant(name, value) {
 assertNotHasOwnProperty(name, 'constant');
 providerCache[name] = value;
 instanceCache[name] = value;
 }
 // 在服务实例化之后进行调用(拦截),拦截函数中注入实例化的服务,可以修改,扩展替换服务。
 function decorator(serviceName, decorFn) {
 var origProvider = providerInjector.get(serviceName + providerSuffix),
  orig$get = origProvider.$get;

 origProvider.$get = function() {
  var origInstance = instanceInjector.invoke(orig$get, origProvider);
  return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
 };
 }

provider方法需要两个参数,一个是服务名(依赖名),另外是工厂方法或者是一个包含依赖和工厂方法的数组。首先通过providerInjector创建工厂方法的一个实例,并添加到providerCache中,返回。

factory方法只是将第二个参数封装成了一个包含$get方法的对象,即serviceProvider,缓存。并不复杂。

而service方法则嵌套注入了$injector服务,即instanceInjector,它会创建构造函数的实例,作为服务对象。

value方法仅仅封装了一个provider,其$get方法返回value值。

constant方法则将value的值分别存入providerCache和instanceCache中,并不需要invoke获取其value值。

而比较特殊且扩展性较高的decorator方法,是在serviceProvider的get方法后面添加一个拦截函数,并通过传递依赖get方法后面添加一个拦截函数,并通过传递依赖delegate来获取原先invoke $get方法返回的服务对象。我们可以通过decorator来对服务进行扩展,删除等操作。

流程

最后,在基本的实现已经完成的基础上,我们走一遍具体的注入流程,更易于我们的深入理解。

angular.module("app",[])
  .provider("locationService",function(){
  ...
 })
 .controller("WeatherController",function($scope,locationService,$location){
  locationService.getWeather()
   .then(function(data){
    $scope.weather = data;
   },function(e){
    console.log("error message: "+ e.message)
   });
 })

我们不关心具体的代码实现,仅仅使用上述代码作为演示。

首先确定AngularJS上下文的范围,并且获取依赖模块(在此处为空);

继续注册服务(依赖),将serviceProvider缓存至providerCache中;

声明控制器;

在此获取injector示例,通过执行invoke函数,获取[“injector示例,通过执行invoke函数,获取[“scope”,”locationService”,”location”]依赖列表,通过location”]依赖列表,通过injector的get方法获取相应的依赖对象。对于scope和scope和location服务而言,在AngularJS初始化时已经注入到Angular中,因此可以获取相应的provider对象,执行相关的方法返回scope和scope和location对象,而locationService则在provider中进行了声明,因此获取到locationServiceProvider对象,通过调用instanceInjector.invoke(locationServiceProvider.$get, locationServiceProvider, undefined, “locationService”)返回locationService对象。

最后将所有的依赖组装成数组[scope,locationService,scope,locationService,location]作为参数传递给匿名函数执行。

总结

至此,依赖注入完成。大家对依赖注入$injector有没有进一步的了解呢?以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

(0)

相关推荐

  • AngularJS学习笔记之依赖注入详解

    最近在看AngularJS权威指南,由于各种各样的原因(主要是因为我没有money,好讨厌的有木有......),于是我选择了网上下载电子版的(因为它不要钱,哈哈...),字体也蛮清晰的,总体效果还不错.但是,当我看到左上角的总页码的时候,479页....479....479....俺的小心脏被击穿了二分之一有木有啊,上半身都石化了有木有啊,那种特别想学但是看到页码又不想学的纠结的心情比和女朋友吵架了还复杂有木有啊,我平常看的电子书百位数都不大于3的好伐! 哎,原谅我吧,我应该多看几本新华字典习

  • AngularJS 依赖注入详解和简单实例

    AngularJS 依赖注入 什么是依赖注入 wiki 上的解释是:依赖注入(Dependency Injection,简称DI)是一种软件设计模式,在这种模式下,一个或更多的依赖(或服务)被注入(或者通过引用传递)到一个独立的对象(或客户端)中,然后成为了该客户端状态的一部分. 该模式分离了客户端依赖本身行为的创建,这使得程序设计变得松耦合,并遵循了依赖反转和单一职责原则.与服务定位器模式形成直接对比的是,它允许客户端了解客户端如何使用该系统找到依赖 一句话 --- 没事你不要来找我,有事我会

  • 详解Angularjs中的依赖注入

    一个对象通常有三种方式可以获得对其依赖的控制权: 在内部创建依赖: 通过全局变量进行引用: 在需要的地方通过参数进行传递 依赖注入是通过第三种方式实现的.比如: function SomeClass(greeter) { this.greeter = greeter; } SomeClass.prototype.greetName = function(name) { this.greeter.greet(name); }; SomeClass能够在运行时访问到内部的greeter,但它并不关心

  • Angular 理解module和injector,即依赖注入

    依赖注入(DI)的好处不再赘言,使用过spring框架的都知道.angularjs作为前台js框架,也提供了对DI的支持,这是javascript/jquery不具备的特性.angularjs中与DI相关有angular.module().angular.injector(). $injector.$provide.对于一个DI容器来说,必须具备3个要素:服务的注册.依赖关系的声明.对象的获取.比如spring中,服务的注册是通过xml配置文件的<bean>标签或是注解@Repository.

  • AngularJs动态加载模块和依赖注入详解

    废话不多说,进入正题... 首先我们看下文件结构: Angular-ocLazyLoad --- demo文件夹 Scripts --- 框架及插件文件夹 angular-1.4.7 --- angular 不解释 angular-ui-router --- uirouter 不解释 oclazyload --- ocLazyload 不解释 bootstrap --- bootstrap 不解释 angular-tree-control-master --- angular-tree-cont

  • AngularJs 动态加载模块和依赖

    最近项目比较忙额,白天要上班,晚上回来还需要做Angular知识点的ppt给同事,毕竟年底要辞职了,项目的后续开发还是需要有人接手的,所以就占用了晚上学习的时间.本来一直不打算写这些第三方插件的学习笔记,不过觉得按需加载模块并且成功使用这个确实是个好处,还是记录下来吧.基于本兽没怎么深入的使用requireJs,所以本兽不知道这个和requireJs有什么区别,也不能清晰的说明这到底算不算Angular的按需加载. 为了实现这篇学习笔记知识点的效果,我们需要用到: angular:https:/

  • AngularJS 依赖注入详解及示例代码

    依赖注入是一个在组件中给出的替代了硬的组件内的编码它们的依赖关系的软件设计模式.这减轻一个组成部分,从定位的依赖,依赖配置.这有助于使组件可重用,维护和测试. AngularJS提供了一个至高无上的依赖注入机制.它提供了一个可注入彼此依赖下列核心组件. 值 工厂 服务 提供者 常值 值 值是简单的JavaScript对象,它是用来将值传递过程中的配置相位控制器. //define a module var mainApp = angular.module("mainApp", []);

  • AngularJS之依赖注入模拟实现

    一.概述 AngularJS有一经典之处就是依赖注入,对于什么是依赖注入,熟悉spring的同学应该都非常了解了,但,对于前端而言,还是比较新颖的. 依赖注入,简而言之,就是解除硬编码,达到解偶的目的. 下面,我们看看AngularJS中常用的实现方式. 方法一:推断式注入声明,假定参数名称就是依赖的名称.因此,它会在内部调用函数对象的toString()方法,分析并提取出函数参数列表,然后通过$injector将这些参数注入进对象实例. 如下: //方法一:推断式注入声明,假定参数名称就是依赖

  • AngularJS $injector 依赖注入详解

    推断式注入 这种注入方式,需要在保证参数名称与服务名称相同.如果代码要经过压缩等操作,就会导致注入失败. app.controller("myCtrl1", function($scope,hello1,hello2){ $scope.hello = function(){ hello1.hello(); hello2.hello(); } }); 标记式注入 这种注入方式,需要设置一个依赖数组,数组内是依赖的服务名字,在函数参数中,可以随意设置参数名称,但是必须保证顺序的一致性. v

  • 详解AngularJS中的依赖注入机制

    依赖注入是一个在组件中给出的替代了硬的组件内的编码它们的依赖关系的软件设计模式.这减轻一个组成部分,从定位的依赖,依赖配置.这有助于使组件可重用,维护和测试. AngularJS提供了一个至高无上的依赖注入机制.它提供了一个可注入彼此依赖下列核心组件. 值 工厂 服务 提供者 常值 值 值是简单的JavaScript对象,它是用来将值传递过程中的配置相位控制器. //define a module var mainApp = angular.module("mainApp", []);

随机推荐