zoukankan      html  css  js  c++  java
  • 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实例。

  • 相关阅读:
    PHP把数组按指定的个数分隔
    主题模型(LDA)(一)--通俗理解与简单应用
    用户活跃度下降40%!七问新浪微博
    天才罗素:知识面横跨哲学 数学和文学 最懂的却是女人
    金刚经---现代解读
    离散数学
    交易已无秘密 一个期货高手的终极感悟
    一位资深交易员的投资感悟(建议收藏)
    F1 score,micro F1score,macro F1score 的定义
    以前曾看到过一个期货童话故事,很有意思,发上来
  • 原文地址:https://www.cnblogs.com/lkiversonlk/p/5122954.html
Copyright © 2011-2022 走看看