angular源码分析:injector.js文件分析——angular中的依赖注入式如何实现的(续)

昨天晚上写完angular源码分析:angular中jqLite的实现——你可以丢掉jQuery了,给今天定了一个题angular源码分析:injector.js文件,以及angular的加载流程,但是想了想,加载流程还是放到后面统一再讲比较好。
如果你没有看过笔者的angular源码分析:angular中的依赖注入式如何实现的,可以点击看看,在其中讲过的内容,我将不会再这里重复,这一期将作那一期的补充。

一、从createInjector函数开始

先省去具体实现,总体看看:函数拥有两个参数,modulesToLoad, strictDi,从单词命名上来看,第一个参数是要没加载的模块,第二参数是严格的依赖注入;另外函数对象本身绑定了一个annotate,在之前我们讲过annotate是一个可以将函数中的参数提出来的函数。

function createInjector(modulesToLoad, strictDi) { ...}
createInjector.$$annotate = annotate;

二、理解createInjector函数的实现

先上代码:

  strictDi = (strictDi === true);
  var INSTANTIATING = {},
      providerSuffix = 'Provider',
      path = [],
      loadedModules = new HashMap([], true),
      providerCache = {  //用存放provider的cache
        $provide: {
            provider: supportObject(provider),
            factory: supportObject(factory),
            service: supportObject(service),
            value: supportObject(value),
            constant: supportObject(constant),
            decorator: decorator
          }
      },
      providerInjector = (providerCache.$injector =
          createInternalInjector(providerCache, function(serviceName, caller) { //调用createInternalInjector生成内部的注入器
            if (angular.isString(caller)) {
              path.push(caller);
            }
            throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
          })),
      instanceCache = {},  //用来存放服务实例的cache
      instanceInjector = (instanceCache.$injector =
          createInternalInjector(instanceCache, function(serviceName, caller) {   
            var provider = providerInjector.get(serviceName + providerSuffix, caller);
            return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
          }));


  forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });

  return instanceInjector;

1.这个函数做了什么。

基本上是定义一堆东西,其中最重要的是providerCache和providerInjector 以及instanceCache和instanceInjector。关于createInternalInjector这个函数,在angular源码分析:angular中的依赖注入式如何实现的中讲过,主要功能是利用提供的cache(第一个参数u)和factory(第二参数),构造一个内部注入器,其本身也是工厂函数。

2.providerCache 和$provide

我们可以这样理解,providerCache中存放的就是各种服务的提供者的实例。比如定义一个服务,叫"dapeng",那么提供者就是"dapengProvider"。而这个容器(providerCache)在初始化时,就默认放入了一个$provide。

      providerCache = {
        $provide: {
            provider: supportObject(provider),
            factory: supportObject(factory),
            service: supportObject(service),
            value: supportObject(value),
            constant: supportObject(constant),
            decorator: decorator
          }
      },

很眼熟吧,这是不是可以用于定义服务的几种方式呢?

3.forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });中的loadModules函数:

  ////////////////////////////////////
  // Module Loading
  ////////////////////////////////////
  function loadModules(modulesToLoad) {
    assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array');
    var runBlocks = [], moduleFn;
    forEach(modulesToLoad, function(module) {
      if (loadedModules.get(module)) return;
      loadedModules.put(module, true);

      function runInvokeQueue(queue) {  //执行调用队列
        var i, ii;
        for (i = 0, ii = queue.length; i < ii; i++) {
          var invokeArgs = queue[i],
              provider = providerInjector.get(invokeArgs[0]);

          provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
        }
      }

      try {
        if (isString(module)) {
          moduleFn = angularModule(module); //加载模块
          runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); //递归加载依赖模块,获取所有模块的run函数定义的代码。
          runInvokeQueue(moduleFn._invokeQueue);  //moduleFn._invokeQueue是什么鬼,先留坑在此
          runInvokeQueue(moduleFn._configBlocks); //moduleFn._configBlocks是什么鬼,也留坑在此
        } else if (isFunction(module)) {
            runBlocks.push(providerInjector.invoke(module));
        } else if (isArray(module)) {
            runBlocks.push(providerInjector.invoke(module));
        } else {
          assertArgFn(module, 'module');
        }
      } catch (e) {
        if (isArray(module)) {
          module = module[module.length - 1];
        }
        if (e.message && e.stack && e.stack.indexOf(e.message) == -1) {
          // Safari & FF's stack traces don't contain error.message content
          // unlike those of Chrome and IE
          // So if stack doesn't contain message, we create a new string that contains both.
          // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
          /* jshint -W022 */
          e = e.message + '
' + e.stack;
        }
        throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:
{1}",
                  module, e.stack || e.message || e);
      }
    });
    return runBlocks;
  }

angularModule是什么鬼,留坑先不讲。可以先理解为,用于加载一个module。
runBlocks将得到一个数组,这个数组的元素是一些函数,这些函数是定义模块后通过run(function(){})注册的函数。
runInvokeQueue函数,将执行调用队列,可以从这个函数的实现上来看,参数queue应该是一个二维数组。[['name','index',params]],这个函数将循环处理queue数组。
** moduleFn._invokeQueue 和 moduleFn._configBlocks** 本期先不讲,留坑在此,等讲“加载流程”再讲。
那么,这个函数最红就是返回的一个函数数组:runBlocks。
可以基本推出:forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });是执行所有的模块中run的代码,而在run的代码执行前,先执行了服务的定义代码和模块config代码。

三、$provider

还是先上代码:

  ////////////////////////////////////
  // $provider
  ////////////////////////////////////

  function supportObject(delegate) {
    return function(key, value) {
      if (isObject(key)) { //如果key是一个对象
        forEach(key, reverseParams(delegate));
      } else {
        return delegate(key, value);
      }
    };
  }

  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});
    };
  }

1.supportObject函数

这里先引用一下reverseParams的实现

   function reverseParams(iteratorFn) {
       return function(value, key) { iteratorFn(key, value); };
   }

来看看下面的代码将会得到什么,就知道这个函数在做什么了:

  function agent(key,vlue){
    console.log(key + '--->'+ value);
  }

  var new_agent = supportObject(agent);
  new_agent({a:123,b:456,c:'abc'});
  new_agent('key','value');

2.provider函数,参数name,provider_

作用,创建服务的提供者,serviceProvider,并且用providerCache保存起来。

3.enforceReturnValue

直接调用instanceInjector.invoke来生成服务。

4.factory

调用provide函数,由函数自身提供一个Provider。

5.service,继续简化服务的定义。

这意味者,可以给service的第二参数传递一个构成函数,service会利用构造函数“new”出一个服务对象来。如果你已经有一个构造函数,需要定义这个构造函数生成的对象为服务,可以考虑使用这个方法。

6.value,继续简化服务的定义。

第二个参数,是一个对象(可以是一个基础类型的值)或者是一个可以返回对象的函数。如果你定义的服务是一个对象,可以考虑用这个方法。

7.constant

可以看到providerCache和instanceCache中的存储用的键是一个,就是说,通过这个函数定义的服务,可以在模块的config阶段和run阶段同时有效。

8.decorator,装饰模式的实现

通过代码分析,可以得到结论:decorator可以对一个已有的服务进行重新装饰。
举例

decorator('exist_service',function($delegate){
   $delegate.add_method = function(){
      console.log('this method is added');
   };
});

上面的代码,将会向服务“exist_service”增加一个add_method方法。
在最新的angular版本中,已经可以采用angular.module('xxx').decorator('exist_service',function($delegate){})的方式来使用decorator,我们讲的这个版本,还只能在config中使用。

上一期:angular源码分析:angular中jqLite的实现——你可以丢掉jQuery了
下一期:angular源码分析:angular的整个加载流程

原文地址:https://www.cnblogs.com/web2-developer/p/angular-6.html