要了解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实例。

AngularJS Injector和Service的工作机制的更多相关文章

  1. android 6.0 高通平台sensor 工作机制及流程(原创)

    最近工作上有碰到sensor的相关问题,正好分析下其流程作个笔记. 这个笔记分三个部分: sensor硬件和驱动的工作机制 sensor 上层app如何使用 从驱动到上层app这中间的流程是如何 Se ...

  2. ASP.NET Web Service如何工作(1)

    ASP.NET Web Service如何工作(1) [日期:2003-06-26] 来源:CSDN  作者:sunnyzhao(翻译) [字体:大 中 小] Summary ASP.NET Web ...

  3. rsync工作机制(翻译)

    本篇为rsync官方推荐文章How Rsync Works的翻译,主要内容是Rsync术语说明和简单版的rsync工作原理.本篇没有通篇都进行翻译,前言直接跳过了,但为了文章的完整性,前言部分的原文还 ...

  4. tomcat中Servlet的工作机制

    在研究Servlet在tomcat中的工作机制前必须先看看Servlet规范的一些重要的相关规定,规范提供了一个Servlet接口,接口中包含的重要方法是init.service.destroy等方法 ...

  5. Android IPC机制—Binder的工作机制

    进程和线程的关系 IPC机制即为跨进程通信,是inter-Process Communication的缩写.是指两个进程之间进行通信.在说进程通信之前,我们的弄明白什么是线程,什么是进程.进程和线程是 ...

  6. angularjs中factory, service和provider

    在Angular里面,services作为单例对象在需要到的时候被创建,只有在应用生命周期结束的时候(关闭浏览器)才会被清除.而controllers在不需要的时候就会被销毁了(因为service的底 ...

  7. Binder的工作机制浅析

    在Android开发中,Binder主要用于Service中,包括AIDL和Messenger,其中Messenger的底层实现就是AIDL,所以我们这里通过AIDL来分析一下Binder的工作机制. ...

  8. rsync(五)工作机制

    当我们讨论rsync时,我们使用了一些特殊的术语来代表不同的进程以及它们在任务执行过程中所扮演的角色.人类为了更方便.更准确地交流,使用同一种语言是非常重要的:同样地,在特定的上下文环境中,使用固定的 ...

  9. 手写Spring框架,加深对Spring工作机制的理解!

    在我们的日常工作中,经常会用到Spring.Spring Boot.Spring Cloud.Struts.Mybatis.Hibernate等开源框架,有了这些框架的诞生,平时的开发工作量也是变得越 ...

随机推荐

  1. c# winfrom实时获取斗鱼房间弹幕

    效果图如下: 通过webBrowser获取,时钟控件刷新弹幕,正则匹配数据,用第二个webBrowser显示弹幕内容.老话,并没完善.请自行完善.有个dll是用来屏蔽webBrowser的声音的,可能 ...

  2. 多线程入门-第四章-线程的调度与控制之sleep

    /* sleep,阻塞当前线程,腾出CPU,让给其他线程 单位是毫秒 静态方法 */ public class ThreadTest04 { public static void main(Strin ...

  3. 在任何mac上用u盘安装OSX和Windows10双系统的方法(支持老电脑、不用Bootcamp)

    Win10是微软主推的,兼容性做的还不错,安装工具做的适应性好. 而且很多Mac机上的Bootcamp不支持u盘安装. 1.先安装OSX,一般电脑自带(建议升级到最新版).如果装了新的ssd,重新安装 ...

  4. wordcount(C语言)

    写在前面 上传的作业代码与测试代码放在GitHub上了 https://github.com/IHHHH/gitforwork 本次作业用的是C语言来完成,因为个人能力与时间关系,只完成了基本功能,扩 ...

  5. Xshell 连接虚拟机特别慢 解决方案

    由于各种原因,xshell连接虚拟机的rhel或者CentOS都几乎是龟速...... 今天专门查了一下解决方案: 原来是ssh的服务端在连接时会自动检测dns环境是否一致导致的,修改为不检测即可,操 ...

  6. 转:docker的核心技术深度剖析

    一.docker是什么 Docker的英文本意是码头工人,也就是搬运工,这种搬运工搬运的是集装箱(Container),集装箱里面装的可不是商品货物,而是任意类型的App,Docker把App(叫Pa ...

  7. PAE 分页模式详解

    2016-11-18 记得之前看windows内核原理与实现的时候,在内存管理部分,看到涉及到PAE模式的部分,提到此模式下可以让系统在虚拟地址还是32位宽的情况下,支持64GB的物理内存或者更多.当 ...

  8. python 通过文件路径获取文件hash值

    import hashlib import os,sys def CalcSha1(filepath): with open(filepath,'rb') as f: sha1obj = hashli ...

  9. 对比python的进程和线程:多线程是假的

    进程,是系统进行资源分配最小单位(拥有独立的内存单元).(python中多进程是真的) 线程,是操作系统最小的执行单位(共享内存资源),比进程还小.(python中多线程是假的,因为cpython解释 ...

  10. PAT 1122 Hamiltonian Cycle[比较一般]

    1122 Hamiltonian Cycle (25 分) The "Hamilton cycle problem" is to find a simple cycle that ...