zoukankan      html  css  js  c++  java
  • 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的整个加载流程

  • 相关阅读:
    webpack压缩图片之项目资源优化
    vue v-cloak 指令 处理页面显示源码
    js 获取url 参数
    element-ui Drawer抽屉组件封装
    js中的this指向
    对js闭包的理解
    vue作用域插槽
    flex布局实战
    vue 组件之间传值
    js 面试题一
  • 原文地址:https://www.cnblogs.com/web2-developer/p/angular-6.html
Copyright © 2011-2022 走看看