Angular.js源码分析之注入器

上一篇主要分析了整体的执行流程,最后说到了要执行注入器部分了,下边就一起来看看angular强大的依赖注入机制是怎样一回事。

注入器

执行过程的那部分代码是这样子的:

// 创建注入器
// 隐藏了注入机制
// 详见createInjector函数
var injector = createInjector(modules, config.strictDi);
// 注入这几个服务执行bootstrapApply
injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
   function bootstrapApply(scope, element, compile, injector) {
    scope.$apply(function() {
      // 进入ng环境(上下文)执行
      element.data('$injector', injector);
      // compile 核心
      // compile返回一个publicLink函数
      // 然后传入scope直接执行
      // 这个scope也就是rootScope
      compile(element)(scope);
    });
  }]
);

首先就要看看createInjector是个什么鬼?

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


  // 加载执行module
  // loadModules得到的是所有的runBlocks
  // 然后执行runBlocks中内容
  forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });

  return instanceInjector;

  // 省略一些代码 看后续分析
}

createInjector.$$annotate = annotate;

从上往下看,首先看providerCache对象有$provide属性,值为对象,提供了provider,factory,servicevalue,constant以及decorator这些方法,很明显猜到了这里就是angular的$provide服务的实现部分。

supportObject

这里使用了supportObject方法,先看看如何实现的:

// supportObject(function(key, val) {})
// 支持对象式传参调用
// 也就是在外边可以类似这样使用
// module.provider({
//   aDirective: function() {}
//   bDirective: function() {}
// })
function supportObject(delegate) {
  return function(key, value) {
    if (isObject(key)) {
      forEach(key, reverseParams(delegate));
    } else {
      return delegate(key, value);
    }
  };
}

其实就是提供了对象式便捷的调用方式。

createInternalInjector

接着往下看,创建了两个注入器实例,一个是providerInjector一个是instanceInjector,而该函数的返回值就是这个instanceInjector。他们是通过createInternalInjector来创建的注入器实例,分析下代码:

////////////////////////////////////
// internal Injector
////////////////////////////////////
// 创建注入器实例
function createInternalInjector(cache, factory) {

  function getService(serviceName, caller) {
    // 省略
  }
  // 执行
  function invoke(fn, self, locals, serviceName) {
  	// 省略
  }

  // 实例化Type 其实就是constructor去构造
  function instantiate(Type, locals, serviceName) {
    // 省略
  }

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

看出来注入器实例有五个方法,按照他们的依赖次序分别分析下。

先看annotate,其实也就是createInjector.$$annotate,他指向的是一个函数:

// injector的annotate方法
// 主要用于推测注入啥(一个是函数 直接 toString处理)
function annotate(fn, strictDi, name) {
  var $inject,
      fnText,
      argDecl,
      last;

  if (typeof fn === 'function') {
    if (!($inject = fn.$inject)) {
      $inject = [];
      // 匿名且没有$inject
      // 只能toString尝试匹配了
      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;
}

可以看出他主要是用来分析注入项的,有三种方式:

  • 直接分析函数参数,通过把函数toString了,然后分析其参数得到正确的依赖项

  • 给函数的$inject属性赋值,是一个数组,就是该函数的依赖项

  • 还有就是数组的形式了,也是本人比较推荐的方式,类似于['$rootScope', function(scope) {}]这样,数组的前n项都是需要注入的参数,最后一项是一个函数,参数一一对应

再来看下get也就是getService方法:

function getService(serviceName, caller) {
  // cache中有的话直接返回就好
  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;
      // 调用factory创建实例(provider或instance)
      return cache[serviceName] = factory(serviceName, caller);
    } catch (err) {
      if (cache[serviceName] === INSTANTIATING) {
        delete cache[serviceName];
      }
      throw err;
    } finally {
      path.shift();
    }
  }
}

可以看到主要逻辑就是通过factory来得到对应的service的值,这里主要看下instanceInjector实例创建的时候传入的factory函数所做的事情:

instanceInjector = (instanceCache.$injector =
  createInternalInjector(instanceCache, function(serviceName, caller) {
  	// 需要得到的是实例,首先要获取provider,然后调用provider的$get得到真正的实例
    var provider = providerInjector.get(serviceName + providerSuffix, caller);
    return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
  }));

下一步就来分析下这个invoke执行的具体代码:

// 执行
function invoke(fn, self, locals, serviceName) {
  if (typeof locals === 'string') {
    serviceName = locals;
    locals = null;
  }

  var args = [],
      // 依赖项
      $inject = createInjector.$$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);
}

逻辑就是得到需要注入的依赖项$inject,然后再得到每一个依赖项的值存到args中,最后执行fn函数,传入各个依赖项。

下边再来看一下instantiate

// 实例化Type 其实就是constructor去构造
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 || null);
  var returnedValue = invoke(Type, instance, locals, serviceName);

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

其实也是调用的invoke,只不过是以Type构造函数实例(原型继承)为this来执行函数Type,也就是说是以构造函数实例化的方式来执行实例化的。可以看到如果执行Type构造函数返回的结果如果是函数或者是函数的话返回就是该结果,否则的话就是返回刚刚创建的实例。

最后一个has简单多了,直接判断了在providerCache中有没有或者是cache(判断到的是instanceCache)中有没有。

到这里createInternalInjector就分析完了,创建出来的每个注入器实例都有上边看到的五个方法:invoke, instantiate, get, annotate, has

loadModules

这是一个真正执行依赖项的方法(不包含run的,run的是返回值,因为run是在最后执行的),很重要,仔细来看:

// 其实更像是一个执行module上的$provider和$injector依赖
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就是Module实例对象 不明白为啥会起了个Fn名字
        moduleFn = angularModule(module);
        // 合并所有的runBlocks
        // 是一个递归调用过程
        // 把依赖模块也一并执行了
        runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
        // 执行$provider的指定方法,如factory,value等
        runInvokeQueue(moduleFn._invokeQueue);
        // 执行$injector的invoke方法
        runInvokeQueue(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 + '\n' + e.stack;
      }
      throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}",
                module, e.stack || e.message || e);
    }
  });
  return runBlocks;
}

从上边的代码可以看出,在整个执行过程中是先执行的_invokeQueue,再执行的_configBlocks,为啥是这样的顺序,不能反过来执行吗?他俩分别是执行的啥?

下边来解答整个疑问,需要先看第二个问题(他俩分别执行了啥?),先看_invokeQueue中存放的是啥。

上一篇中分析angular.module的执行逻辑的时候,看到返回实例的provider,factory,service,value,constant,decorator,animation,filter,controller,directive这些方法,他们真正执行的代码其实是往_invokeQueue中存放类似于[provider, method, arguments]这样的数组对象进去的(当然不是立即执行的,被别的依赖了之后再去执行),所以说执行_invokeQueue也就是相当于先去执行得到上边的这些方法的过程,其实也就是调用对应的Provider对应的方法的过程。

在看_configBlocks,保存的配置逻辑代码块,同样是在angular.module的定义中有这样的代码var config = invokeLater('$injector', 'invoke', 'push', configBlocks);,也就是说module的config函数其实是可以依赖注入的,可以写入依赖项(虽然只能是常量 provider可以注入)。

到这里就明了了,如果说执行_invokeQueue_configBlocks的顺序反过来的话,那么当前模块的常量和provider是不能注入到config中的。

下边来个例子来说明下这个过程:

// 注意为啥这里注入的不是myService 而是myServiceProvier ,请看下边关于provider的分析
var app = angular.module('app', [], ['myServiceProvier', function appConfig(myServiceProvier) {}]);
app.factory('myService', ['$http', function($http) {}]);
app.run(['$rootScope', function($rootScope) {}])

首先执行了factory,此时只是往app._invokeQueue中插入了新的项['$provider', 'factory', [['myService', ['$http', function($http) {}]]]],而默认定义module的时候传入的appConfig函数则是被push进了app._configBlocks中了,插入的项就是[‘$injector’, ‘invoke’, [[‘myServiceProvier’, function appConfig(myServiceProvier) {}]]]`。

再执行run方法,其实只是往app._runBlocks中增加了新的项['$rootScope', function($rootScope) {}]

现在来看当loadModules执行到了app模块的时候是怎样的过程:

  1. moduleFn就是app

  2. requires无依赖,这里就忽略,然后得到runBlocks,长度是1,里边的内容就是['$rootScope', function($rootScope) {}]

  3. 执行_invokeQueue,他的长度是1,到runInvokeQueue函数中,invokeArgs就是['$provider', 'factory', [['myService', ['$http', function($http) {}]]]],所以实际的执行就相当于$provider.factory(['myService', ['$http', function($http) {}]]),然后$provider.factory其实就是后边会分析的在createInjector函数中的factory函数,作用也就是给providerCache对象设置myServiceProvier的值为{$get: ['$http', function($http) {}]}

  4. 继续执行_configBlocks,长度也是1,到runInvokeQueue函数中,invokeArgs就是['$injector', 'invoke', [['myServiceProvier', function appConfig(myServiceProvier) {}]]],实际调用的就是$injector.invoke(['myServiceProvier', function appConfig(myServiceProvier) {}]]),这个函数上边分析过,过程就是找到依赖项myServiceProvier(此时在上一步的时候就已经给providerCache设置过myServiceProvier了),然后执行appConfig

到这里,复杂的loadModules分析就算是完成了,此时记得他的返回值,也就是runBlocks的内容['$rootScope', function($rootScope) {}]

createInjector

回到createInjector中,拿到loadModules的返回值后,直接循环,这样执行了:

instanceInjector.invoke(fn)

通过instanceInjector这个注入器直接执行了,也就是调用invoke方法,记得上边的runBlocks的内容['$rootScope', function($rootScope) {}]吗,也就是说传入invoke的就是他。逻辑不多说了,已经分析过了。

不要忘记在createInjector中这些函数:provider,factory,service,value,constant,decorator还没有分析,下边一一来看。

首先看看provider函数的实现:

// 构造名字为name的provider
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_;
}

其实很简单,就是给providerCache设置值的,只不过在设置的时候会加上providerSuffix也就是Provider,所以说,在你需要注入一些provider的时候,名字后边是要加上Provider的。

接着看factory

// factory其实就是provider的简写方式
// 省略了$get那一层而已
// enforce是否强制返回值 如果说不是false的话 那么就会去强制
// 必须有 没有的话 就会报错
// 
// 注意这里的factory和真正咱们在外边
// 通过module的实例调用factory的区别,
// 通过实例调用factory的时候其实是通过supportObject包装
// 过的,而包装过得函数调用时最多只有两个参数
// 所以是根本不会存在enforce配置项的
function factory(name, factoryFn, enforce) {
  return provider(name, {
    $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
  });
}

注释已经写的很清楚了,就是provider简写方法而已。

然后看service

function service(name, constructor) {
  return factory(name, ['$injector', function($injector) {
    // 参看instantiate逻辑
    return $injector.instantiate(constructor);
  }]);
}

其实就是对于以构造函数实例化的形式来使用factory的,举一个例子:

如果用factory可能这样写:

module.factory('xx', ['$http', function($http) {
	return {
		a: function() {}
	}
}]);

如果用service的话,可能就是这样:

module.service('xx', ['$http', function($http) {
	this.a = function() {};
}]);

再看value:

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

简单的对factory封装,只需要传入name和val,不必包含函数部分,在依赖的时候,其值就是val。

下边看constant

// 常量 直接写入缓存对象
// 所以是在provider和instance(run)中都是
// 可以住人的
function constant(name, value) {
  assertNotHasOwnProperty(name, 'constant');
  providerCache[name] = value;
  instanceCache[name] = value;
}

所以在任何时候都是可以去注入常量的。这里并看不出来为啥会有常量的效果,真正的魔法在模块实例化对象那里:

// 注意这里是怎么实现常量效果的
// 通过给 invokeQueue unshift
// $provide的constant方法
// 所以是一个逆序的过程
// [后边设定的值,最开始设定的值]
// 所以说执行完之后 就是得到的 最开始设定的那个值
constant: invokeLater('$provide', 'constant', 'unshift')

然后这个api是暴露在module的实例上的。

最后看下decorator:

// 装饰器
// 也就是保存原来的$get
// 然后重新修改下
function decorator(serviceName, decorFn) {
  var origProvider = providerInjector.get(serviceName + providerSuffix),
      orig$get = origProvider.$get;

  origProvider.$get = function() {
    var origInstance = instanceInjector.invoke(orig$get, origProvider);
    // 把之前的实例当做locals传入到invoke中
    return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
  };
}

invoke

angular初始化是要进行编译的,那编译的调用也就在createInjector之后,看代码:

// 注入这几个服务执行bootstrapApply
injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
   function bootstrapApply(scope, element, compile, injector) {
    // 编译逻辑
  }]
);

可以看出拿通过createInjector创建的注入器实例来执行bootstrapApply代码,当然会继续走上边以及分析过的invoke逻辑,这里不再细说。但是这里看依赖注入的参数,这里出现了一个关键的$rootScope,他是angular中一个极其重要的东西,留到下篇分析。

结语

本篇主要是分析了整个注入器原理以及过程,希望对其没有深入了解的你有所帮助。当然,更欢迎讨论、吐槽!

发布于: 2015年 10月 22日