首页 > 网站 > WEB开发 > 正文

AngularJS源码分析之依赖注入$injector

2024-04-27 14:10:44
字体:
来源:转载
供稿:网友

AngularJS源码分析之依赖注入$injector

开篇

随着javaEE的sPRing框架的兴起,依赖注入(IoC)的概念彻底深入人心,它彻底改变了我们的编码模式和思维。在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可以使用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方法都是基于此进行构建的。

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 instanceInjec
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表