首页 > 网站 > WEB开发 > 正文

【原创】angularjs1.3.0源码解析之service

2024-04-27 14:15:54
字体:
来源:转载
供稿:网友

【原创】angularjs1.3.0源码解析之service

Angular服务

在angular中,服务(service)是以提供特定的功能的形式而存在的。

angular本身提供了很多内置服务,比如:

  1. $q: 提供了对PRomise的支持。
  2. $http: 提供了对Ajax的支持。
  3. $location: 提供了对URL的解析。
  4. ...

这些服务,或多或少地会出现在我们控制器(controller)、指令(directive)或者某个被依赖注入的函数中,帮助我们实现特定的功能。

当然,除了调用官方的服务,我们也可以定义适合于自己业务的“服务”,这个后面会讲到。

服务的本质

从代码层面来看,服务其实一个单例(可以是任何类型),被所有的调用者所共享(在一个angular应用生命周期内,它只会被初始化一次)。

服务的调用

所有的服务,都是通过依赖注入的方式被调用的。

像这样,简单地调用$timeout服务(代码在这里):

var app = angular.module('myApp', []);// 注入$timeout服务app.run(['$timeout', function ($timeout) {    $timeout(function () {        console.log('hello');    },2000).then(function () {        console.log('world');    });}]);

显然,要想弄清楚服务,必然得先说一说依赖注入,往下看~~

依赖注入(DI)

angular在前端引入了DI的概念,目的是更快更轻巧地引入函数执行时所需要的依赖项(这些依赖项就是前面说到的服务)。

设想:我们在写一个异步请求数据的函数,我们会调用库异步api:

用jquery,我们会这样写:

function getData () {    $.ajax({        ...    });}

用angular,我们可以这样写:

function getData ($http) {    $http({        ...    });}
  1. 从功能层面上看:都一样。
  2. 从代码层面上看:jquery方面,通过$.ajax调用(万一$是全局的且函数嵌套很深呢?);而angular方面,则是通过形参$http直接调用且更清晰。
  3. 从性能方面上看:jquery方面,$.ajax方法在程序开始就会被声明初始化;而angular方面,事实上,只在$http第一次被调用时才会被初始化。

另外:设想一下,如果angular像jquery这样做:

angular.$http = function () {...}

那么对于越来越多的自定义服务而言:

angular.$xxx = function () {...}

这样会使得,angular这个全局变量一开始就会变得很臃肿而且受到到污染,在调用方面也更是多了一层全局访问。

综上:angular采用依赖注入服务的形式来使用特定功能更为恰当(当然,以上都是个人理解)。


那么来看看依赖注入的几种方式(代码在这里):

1.隐式注入(通过函数toString,正则匹配出依赖项,如:$log

var ng = angular.module('ng');// 隐式注入ng.run(function ($log) {    $log.error('隐式注入');});

2.显示注入(通过数组或者fn.$inject,指明依赖项,如:$log

var ng = angular.module('ng');// 显示注入ng  // 数组指明依赖项  .run(['$log', function (log) {    log.error('数组显示注入');  }])  // fn.$inject指明依赖项  .run(fn);function fn (log) {    log.error('$inject显示注入');}fn.$inject = ['$log'];

问:angular是如何解析函数的依赖项的?

答:通过一个叫做annotate的函数解析,解析的步骤如下:

  1. 如果目标是函数fn

    • 优先获取fn.$inject作为依赖项(显示注入)
    • 其次是通过fn.toString(),然后正则匹配出参数作为依赖项(隐式注入)
  2. 如果目标是数组arr,那么arr.slice(0, arr.length - 1)作为依赖项(显示注入)

我们可以这样做测试:

function fn ($log, $timeout) {    // ...}console.log(angular.injector.$$annotate(fn)); // ["$log", "$timeout"]

注意:由于线上代码往往会压缩混肴,所以这时只能使用显示依赖注入的方式。

ok,到这里我们知道可以通过annotate函数解析出一个函数(或数组)的依赖项,那么如何实现注入的呢?

答案:一切的一切都跟内置的服务$injector有关。


是否还记得angular程序启动时,会执行这句:

var injector = createInjector(modules, config.strictDi);

这是能够使用依赖注入的根本原因,因为它创建了$injector服务。


仔细的分析createInjector方法到底做了些什么:

  1. 创建了两个injector实例以及两个cache,用于依赖注入
  • providerInjector(服务提供者的依赖注入)
  • instanceInjector(服务的依赖注入)
  • providerCache(服务提供者缓存)
  • instanceCache(服务缓存)
  1. 加载程序所需要的所有(依赖)模块
  • 注册依附在模块上的指令,服务,控制器,过滤器等
  • 执行模块的配置函数(即myModule.config(fn)
  • 执行模块的运行函数(即myModule.run(fn)

这里就需要说明下 服务提供者(provider)服务(instance)的关系?

服务的提供者:

  1. 顾名思义,是用来提供服务的(从程序的角度来说就是,初始化服务实例并返回)
  2. 另外,它还有一个作用就是:配置服务

$rootScope服务为例:它对应的服务提供者就是$rootScopeProvider(注意:$injector$provide除外)。

来个例子吧(代码在这里):

服务的注入:

var app = angular.module('myApp', []);app    .run(function ($rootScope) {        $rootScope.msg = 'hello world';    });

服务提供者的注入:

var app = angular.module('myApp', []);app    .config(function ($rootScopeProvider) {        $rootScopeProvider.digestTtl(5);   // 配置digest循环的ttl    });

总结:上面的两个例子进行了依赖注入,不同的只是注入的对象(一个是服务,一个是服务提供者)。

造成这种不同的根本原因是什么?

  1. run函数在内部使用instanceInjector.invoke(fn),获取的是依赖的服务实例,并传递给fn函数,供调用,提供服务功能。

  2. config函数在内部使用providerInjector.invoke(fn),获取服务提供者,并传递给fn函数,供调用,提供服务配置。

所以说,就有了两个维度上的差别:

  1. instanceInjector:针对的是服务实例的注入。
  2. providerInjector:针对的是服务提供者的注入。

知道了原理后,对于上面的两个注入的例子我们可以这样改写,代码在这里:

服务的注入:

var app = angular.module('myApp', []);app    .config(function ($injector) {  // 获取providerInjector        $injector.invoke(function ($rootScopeProvider) {            $rootScopeProvider.digestTtl(5);  // 配置digest循环的ttl            console.log($rootScopeProvider)        });    });

服务提供者的注入:

var app = angular.module('myApp', []);app    .run(function ($injector) {  // 获取instanceInjector        $injector.invoke(function ($rootScope) {            $rootScope.msg = 'hello world';        });    });

另外:对于instanceInjector,我们也可以通过angular.injector(...)自己创建,所以我们还可以这样改写,(代码在这里):

var injector = angular.injector(['ng']);injector.invoke(function ($rootScope, $compile) {      $rootScope.$apply(function () {          $compile(document.body)($rootScope);          $rootScope.msg = 'hello world';      });  });

注意:以上的改写只是为了说明原理,一般都不建议这样做,因为angular会帮我们做这部分工作,我们只要按要求引入依赖即可。


再然后,说说服务提供者如何提供服务?

依旧拿$rootScope举例:

$rootScope的提供者$rootScopeProvider有一个(可以依赖注入的)$get函数(或者数组),负责返回服务实例(可以是任何类型)。


再说说cache的问题?

之前说过angular的服务实例是一个单例,归根结底的原因就是cache的存在,才使得服务实例的构造函数只会执行一次。

上面说过了,有两个cache对象:

  1. providerCache(服务提供者缓存)
  2. instanceCache(服务实例缓存)

当我们注册一个服务,叫做'xx'(准确的说是服务提供者)时,大部分情况会经过包装(或者实例化)转换成包含$get函数(或者数组)的对象,然后存储到providerCache中,所以:

  1. 如果是对服务提供者(xxProvider)的依赖,其实都是根据键值'xxProvider'providerCache中获取的,如果获取不到则报错。
  2. 如果是对服务(xx)的依赖,则是优先根据键值'xx'instanceCache中获取,
  • 如果获取到,则返回。
  • 如果获取不到,那么就根据键值'xxProvider'获取服务提供者,然后通过调用它的$get方法返回服务实例,最后再缓存进instanceCache中。

服务的注册

之前,我们举例说的都是内置服务,现在我们看看如何自定义属于适合自己业务的服务?

angular给我们提供了以下几种注册服务的方式:

  1. provider:注册可配置的服务。

  2. factory:注册服务的快捷方式,基于provider方法,不可配置。

  3. service:利用构造函数注册服务,基于factory方法。

  4. value:注册简单的返回值服务,不支持依赖注入,基于factory方法。

  5. constant:注册常量服务,且在config函数中可调用。

  6. decorator:拦截装饰现有服务,用于扩展或者重写服务。


我们举例来说明,(代码在这里):

1.provider方法

var app = angular.module('myApp', []);// provider 可配置服务app.provider('A', ['$logProvider', function (LogProvider) {        var isDebug = true;        this.debugEnabled = function (flag) {        LogProvider.debugEnabled(!!flag);    };        this.$get = ['$log', function (log) {        return {            debug: function (msg) {                log.debug(msg);            }        };    }];}]);

provider方法创建了可配置的服务A,它的可配置体现在:我们可以在config函数中调用它的服务提供者(AProvider)的debugEnabled方法进行配置,如下:

app    .
上一篇:node.js

下一篇:JS函数重载解决方案

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表