要了解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. datasnap 授权验证DSAuthenticationManager方法应用

    服务端 1.服务端只需要增加DSAuthenticationManager1并且把dshttpservice的AuthenticationManager属性设置为DSAuthenticationMan ...

  2. Myeclipse更新SVNStatusSubscriber 时报告了错误。1 中的 0 个资源已经同步。

    1.先确认SVN服务是否能连接,或权限. 方法:在项目目录下右键选择repo-browser 能打开就表示正常. 2.同样在项目目录下选择cleaup 选择下面3个选项 clean up workin ...

  3. 剑指Offer——机器人的运动范围

    题目描述: 地上有一个m行和n列的方格.一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子. 例如,当k为18时,机器人 ...

  4. 009-Shell 函数

    一.函数定义 linux shell 可以用户定义函数,然后在shell脚本中可以随便调用. shell中函数的定义格式如下: [ function ] funname [()] { action; ...

  5. windows平台mongoDB安装配置

    一.首先安装mongodb 1.官网下载mongoDB:http://www.mongodb.org/downloads,选择windows平台.安装时,一路next就可以了.我安装在了F:\mong ...

  6. linux上scp远程复制的使用

    一.实例展示 两台机器IP分别为:A.104.238.161.75,B.43.224.34.73. ------把别的服务器上的资源拿到自己的上边 在A服务器上操作,将B服务器上/home/lk/目录 ...

  7. matlab手写神经网络实现识别手写数字

    实验说明 一直想自己写一个神经网络来实现手写数字的识别,而不是套用别人的框架.恰巧前几天,有幸从同学那拿到5000张已经贴好标签的手写数字图片,于是我就尝试用matlab写一个网络. 实验数据:500 ...

  8. arya使用流程

    1.github中的项目clone到本地(路径在最后),然后将arya文件夹复制到你的django工程中作为一个独立的app,该app实现了RBAC(基于角色的权限访问控制Role-Based Acc ...

  9. linux_rpm命令

    rpm 常用命令 1.安装一个包 # rpm -ivh 文件名 2.升级一个包 # rpm -Uvh文件名 3.移走一个包 # rpm -e文件名 4.安装参数 --force 即使覆盖属于其它包的文 ...

  10. NC学习笔记

    NC基础技术  一.NC开发基本概念        1.单据:一般是由表头,表体,表位组成.                   表头:一般是一些主信息,例如日期,项目等               ...