JesseZhao1990 / blog

learing summary
MIT License
62 stars 7 forks source link

angularjs源码解析系列(2)---依赖注入 #159

Open JesseZhao1990 opened 5 years ago

JesseZhao1990 commented 5 years ago

angularjs的一个很大的优势和特点就是依赖注入,下面我们分析一下angularjs源码中是怎么实现的。

  1. 在调用bootstrap的时候,调用createInjector来创建一个注射器进行注入。 image

  2. createInjector这个方法的简单的代码如下

image

从上边的代码中,我们可以简略的看到,主要是定义了一些变量,然后调用createInternalInjector得到providerInjector,再次调用createInternalInjector得到instanceInjector。然后用loadModules依次加载模块,最后返回instanceInjector

下面介绍一下createInjector里面的几个变量

下面我们分析createInternalInjector。大致代码如下

image

从截图的代码可以看到createInternalInjector返回一个对象,对象上挂载了invoke,instantiate,get,annotate,has 这些方法。这里我们需要关注的是createInternalInjector被调用两次,这两次的返回值分别是providerInjector和instanceInjector。providerInjector这个内部注射器主要是用来处理provider的,instanceInjector主要是处理provider生成的单例的。

这个地方不太容易读懂,原因就在于createInternalInjector这个函数经过了抽象,把创建providerInjector和instanceInjector的过程抽象成一个函数。读起来容易混淆。这里需要好好去琢磨一下。

本段话如果弄不明白,先不要着急,你只需要先知道这个结论即可。随着咱们的对源码的慢慢剖析,这些最终都将比较容易理解。angular中的注射器有两种,一种是providerInjector,一种是instanceInjector。平时咱们一般写的都是instanceInjector。instanceInjector是负责管理providerInjector的实例的。刚刚咱们也分析了。providerInjector和instanceInjector上挂载的方法是一样的。但是作用却是大不相同。

下面我们具体分析一下这四种方法和其分别在两种注射器中的作用

1. get

image

由上面的截图中的代码可以看出,get其实就是getService。getService的详细代码如下

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

从上边的代码可以看到,providerInjector的get方法是尝试获取providerCache中缓存的provider,如果没有则执行工厂函数。从下面的截图可以看出,factory是一个会抛出异常的函数,也就是说当我们尝试去获取并不存在的provider的时候,会在控制台报错,提示 Unknown provider

image

image

instanceInjector的get方法是尝试获取instanceCache中缓存的provider创建的实例。如果没有,则执行穿进去的工厂函数创建一个。

image

image

2. annotate

该方法主要用于推断注入,我们知道angular中的依赖注入,可以用推断式和行内注入,推断式注入主要是通过调用函数的toString方法,得到函数的签名,用正则分析出函数的形参。从而得到依赖。 行内注入主要是通过截取数组的0到数组长度减1获取到依赖。

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

/**
 * $injector.annotate('xxx')表示获得xxx的所有依赖项(名字)
 * @param {*} fn 
 */
function annotate(fn) {
  var $inject,
      fnText,
      argDecl,
      last;

  if (typeof fn === 'function') {
    if (!($inject = fn.$inject)) {
      $inject = [];
      if (fn.length) {
        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;
}

3. invoke

执行传入的函数(数组函数),比如

someModule.factory('greeter', ['$window', fn]);

function fn($window) {
console.log(1111);
}

invoke此时执行的是就是fn函数,先是分析fn的依赖,然后去拿依赖,如果没有相关的依赖,会先创建相关的依赖。然后执行fn

这里有个有点绕的问题再次跑出来。大家可以想象一下,执行providerInjector.invoke 和instanceInjector.invoke的区别是什么?

也可以看一下下面这个问题产生的根源是什么?demo我写在了codepen中。 https://codepen.io/zhaojianxin/pen/BGxmaa

为什么$provide在config中调用就没有问题,在controller中调用就会报错?

    function invoke(fn, self, locals){
      var args = [],
          $inject = annotate(fn),
          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)
        );
      }
      if (isArray(fn)) {
        fn = fn[length];
      }

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

4. instantiate

用于实例化provider或者实例化服务的。

    function instantiate(Type, locals) {
      var Constructor = function() {},
          instance, returnedValue;

      // 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) {}]);
      Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype;
      instance = new Constructor();
      returnedValue = invoke(Type, instance, locals);

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