AngularJS Injector和Service的工作机制

要了解angularJS里的injector和Service是如何工作的,需要阅读/src/auto/injector.js。另外要结合/src/loader.js才能明白它的应用场景。

auto/injector.js

辅助函数

在javascript中,如果对一个函数调用toString方法,会打印出这个函数的source code。angularJS的inject也是基于这个功能实现的。因此,在injector.js文件的一开头定义了几个辅助函数:

  1. extractArgs(fn)
    这个函数将从fn.toString()中提取出参数字段作为一个string返回。
  2. annotate(fn)
    这是根据函数签名为函数添加注解(annotation)的方法。

injector的定义

angularJS中一共存在两个injector:providerInjector和instanceInjector。都是由createInternalInjector(cache, factory)函数创建的,要了解injector的组成必须先看明白createInternalInjector这个函数。

一个injector中有以下几个公开方法:

  • invoke(fn, self, locals, serviceName)
  • instantiate(Type, locals, serviceName)
  • get(serviceName, caller)
  • annotate
  • has(name)

另外在createInternalInjector中定义了一个内部方法injectionArgs(fn, locals, serviceName), 该方法首先获取fn的函数签名中的参数列表,根据参数名从locals中查找参数值,当locals中不存在时,调用injector的get方法去查找参数值,最后列表返回所有参数值,就这样AngularJS根据函数签名获取了函数依赖。在invoke, instantiate中均会用这个方法来获取依赖然后注入到函数中。

接下来我们一一了解injector的几个公开方法:

  1. get(serviceName, caller)

    • 当前cache中有serviceName键,但是键值为INSTANTIATING,表示出现了循环依赖,丢出错误,否则返回键值。
    • 而cache中不存在时,将键值置为INSTANTIATING,然后调用factory(service, caller)去根据service获取实例,将结果置为键值。这样如果在创建过程中再次遇到该依赖,就会发现循环依赖。另外创建成功后,该值也就留在了cache中。
  2. invoke(fn, self, locals, serviceName)

    • 首先调用injectionArgs(fn, locals, serviceName)获取函数依赖。
    • 用获取的依赖来调用该函数,返回结果。
  3. instantiate(Type, lcoals, serviceName)

    与invoke类似,利用Type的函数签名获取依赖然后实例化Type

总结一下,injector就是围绕其创建时传入的cache和factory工作的,其get方法会首先从cache中取缓存,否则则通过factory方法去生成实例返回。其invoke方法会根据函数签名去获取参数依赖,然后调用函数。

AngularJS中的Injector

上一节讲了injector的基本结构,AngularJS中一共有两个injector,一个负责serviceProvider的注入,一个负责service实例的注入。阅读createInjector(modulesToLoad, strictDi)函数看到它们的初始化过程。

  • providerInjector
    函数首先创建了providerCache,然后用这个cache和一个factory方法创建了providerInjector,但是该factory方法只会报出"unknown provider"错误。那如何往cache中添加serviceProvider呢?这里providerCache中预置了一个键值为"$provide"的对象,该对象提供一系列方法provider, factory, service, value, constant等供调用者向providerCache中添加serviceProvider.

  • instanceInjector
    然后函数创建的就是instanceProvider,这里的初始cache是一个空字典,一旦需要的实例不存在cache中时,factory函数就会首先使用providerInjector.get去获取该service的provider,然后再通过instanceProvider.invoke方法去调用serviceProvider.$get方法,这个过程中 又会继续实例化该service的各种依赖,最终获取到service实例,同时根据injector的工作机制,该实例自此被加入到instanceInjector的cache中,下次就可以直接获取了。
    函数同时将一个只会返回instanceInjector的provider以"$injector"为键加入了providerCache中,所以instanceInjector可以通过"$injector"获取到它自身。

  • loadModules
    在两个injector都初始化完成后,函数调用loadModules(modulesToLoad),获得一个runBlocks列表,然后对其中每一个block调用instanceInjector.invoke(block),最后返回instanceInjector。很明显,这个过程中加载了modules中各种service的定义,要了解这个工作过程,我们需要看一下loadModules定义。

    • loadModules(modulesToLoad)
      该函数首先把每个module以及每个module所require的module中的runBlocks都合并到一个列表中。
      然后调用:
            runInvokeQueue(moduleFn._invokeQueue);
            runInvokeQueue(moduleFn._configBlocks);
    

    至于runInvokeQueue,在后面遇到使用时再讲明白。总之loadModules函数将合并的runBlocks列表返回。

loader.js

要明白各个service是怎么加载的,我们需要看一下angularJS对于Modules的定义,这里需要了解一下loader.js。

AngularJS中的Modules

loader.js中的module(name, requires, configFn)就是我们最常使用的angular.module("***", [], [])方法。它返回一个moduleInstance,其中自带了_invokeQueue, _configBlocks和_runBlocks三个队列,不过这三个队列一开始都是空的。
module函数中定义了两个重要的内部方法invokeLater和invokeLaterAndSetModuleName,了解了这两个方法才能明白上面三个queue是怎么用的。

  • invokeLater(provider, method, insertMethod, queue)
    当未指定queue时,queue默认为module的invokeQueue.
    当未指定insertMethod时,默认为"push"。
    该方法返回一个方法f,当f被调用时,向queue中通过insertMethod加入一个[provider, method, f的arguments]组成的数列。queue最后就被前述injector.js中的runInvokeQueue调用了,现在我们就可以看看runInvokeQueue的定义了。

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

    对于queue中的参数arg,从providerInjector中get(arg[0]),然后调用arg[1]方法,参数是arg[3]。arg[0]是invokeLater的参数中的provider, arg[1]是method,arg[3]是返回函数f被调用时候的参数。
    对于仍然糊涂的同学,我们看看moduleInstance中的value方法的定义:

      value : invokeLater("$provide", "value"),
    

    因此angular.module("A").value(name, val),会在module A的invokeQueue中插入['$provide', 'value', [name, val]],这个queue会在加载module A的时候被传递给runInvokeQueue作为参数,执行providerInjector.get("$provide")['value'](name, val), 结合之前providerInjector的初始化过程,该函数执行结果为加载了一个返回val的名为name的service。

  • invokeLaterAndSetModuleName(provider, method)
    这个方法的返回值也是一个方法,返回方法的签名是(name, factory),调用时会将factory.$$moduleName置为module的name,然后直接想invokeQueue中push进[provider, method, arguments]
    我们可以再看看module.directive的定义:

        directive: invokeLaterAndSetModuleName('$compileProvider', 'directive');
    

    因此当我们调用angular.module("A").directive("DA", function(){})时,最终向invokeQueue中插入了["$compileProvier", "directive", ["DA", function(){}]]参数。不用担心$compileProvider现在还没有,因为这只是插入invokeQueue,只有在模块被加载时才会执行,而$compileProvider属于ng模块,该模块会被默认加载。

    于是我们在模块中定义的各种service都会被加载到queue中,在模块加载时去初始化service的provider,然后在injector做依赖注入时,从provider中生成service实例。

posted @ 2016-01-11 23:27  白羊座的Jerry侃  阅读(1225)  评论(0编辑  收藏  举报