Angular服务深度剖析(四)

在angular中, 服务(service)

是以提供 特定的功能

的形式而存在的。

angular本身提供了很多 内置服务

,比如:

  1. $q: 提供了对promise的支持。1. $http: 提供了对ajax的支持。1. $location: 提供了对URL的解析。1. &8230;这些服务,或多或少地会出现在我们控制器(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. 从功能层面上看:都一样。1. 从代码层面上看:jquery方面,通过 $.ajax

调用(万一 $

是全局的且函数嵌套很深呢?);而angular方面,则是通过形参 $http

直接调用且更清晰。1. 从性能方面上看: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

作为依赖项(显示注入)1. 其次是通过 fn.toString()

,然后正则匹配出参数作为依赖项(隐式注入)1. 如果目标是数组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(服务提供者的依赖注入)1. instanceInjector(服务的依赖注入)1. providerCache(服务提供者缓存)1. instanceCache(服务缓存)1. 加载程序所需要的所有(依赖)模块注册依附在模块上的指令,服务,控制器,过滤器等1. 执行模块的配置函数(即 myModule.config(fn)

)1. 执行模块的运行函数(即 myModule.run(fn)

)这里就需要说明下 服务提供者(provider)

与 服务(instance)

的关系?

服务的提供者:[br]1. 顾名思义,是用来 提供

服务的(从程序的角度来说就是,初始化服务实例并返回)[br]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函数,供调用,提供服务功能。

  1. config

函数在内部使用 providerInjector.invoke(fn)

,获取服务提供者,并传递给fn函数,供调用,提供服务配置。

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

  1. instanceInjector:针对的是 服务实例

的注入。1. 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(服务提供者缓存)1. instanceCache(服务实例缓存)当我们注册一个服务,叫做 'xx'

(准确的说是服务提供者)时,大部分情况会经过包装(或者实例化)转换成包含 $get

函数(或者数组)的对象,然后存储到 providerCache

中,所以:

  1. 如果是对 服务提供者

(xxProvider)的依赖,其实都是根据键值 'xxProvider'

从 providerCache

中获取的,如果获取不到则报错。1. 如果是对 服务

(xx)的依赖,则是优先根据键值 'xx'

从 instanceCache

中获取,如果获取到,则返回。1. 如果获取不到,那么就根据键值 'xxProvider'

获取服务提供者,然后通过调用它的 $get

方法返回服务实例,最后再缓存进 instanceCache

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

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

  1. provider:注册 可配置

的服务。

  1. factory:注册服务的 快捷方式

,基于provider方法,不可配置。

  1. service:利用 构造函数

注册服务,基于factory方法。

  1. value:注册简单的返回值服务, 不支持依赖注入

,基于factory方法。

  1. constant:注册 常量

服务,且在config函数中可调用。

  1. 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    .config(function(AProvider) {        AProvider.debugEnabled(false); // 配置服务    })

另外,可以看到 AProvider

还有一个 必不可少

的(可依赖注入的) $get

数组(当然也可以是函数),它则负责返回服务实例(就像之前说的那样),因此,我们在 run

函数中才可以这样调用:

app    .run(function (A) {       A.debug('A provider') ;    })

2.factory方法

var app = angular.module('myApp', []);// factory app.factory('B', function ($log) {   return {       debug: function (msg) {           $log.debug(msg);       }   };    });factory

方法是注册了服务B,和服务A基本一样,唯一不同的就是它 不可配置

其实factory方法是基于provider方法的,其实是注册服务的快捷方式,内部实现是这样的:

function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); }

也就是说,我们在factory中注册的函数,在provider中被当作 $get

,那么函数的返回值自然就是服务实例,所以可以这样调用:

app    .run(function (B) {       B.debug('B factory');    });

3.service方法

var app = angular.module('myApp', []);// service 构造函数app.service('C', function ($log) {    this.debug = function (msg) {        $log.debug(msg);    };});service

方法注册了服务C,与factory方法不同的是,它的函数不需要返回值,而是以构造函数实例化的形式生成服务实例,

所以可以这样调用:

app.    .run(function (C) {       C.debug('C service');    });

其实service方法是基于factory方法的,归根结底也是基于provider方法,内部实现是这样的:

function service(name, constructor) {  return factory(name, ['$injector', function($injector) {    return $injector.instantiate(constructor); // 实例化构造函数  }]);}

4.value方法

var app = angular.module('myApp', []);// valueapp.value('E', {    debug: function (msg) {        console.log(msg);    }});value

方法注册了服务E,服务实例就是注册的值,这里是一个对象(当然,可以是其他任何类型)。

比如:

app.value('F', 'hello world');  // 一个字符串app.value('G', function () {    // 或者一个函数    console.log('hello world');});

其实value方法,也是基于factory方法的,内部实现如下:

function value(name, val) { return factory(name, valueFn(val)); }function valueFn(value) {return function() {return value;};}

可见, value

方法就是将这个注册的值作为服务实例返回,只是中间过程不再有 依赖注入

使用方法:

app    .run(function (E) {       E.debug('E value');    });

5.constant方法

var app = angular.module('myApp', []);// constant 常量app.constant('D', 'D constant');app.constant('D', 'modified'); // 无效constant

方法是用来定义 常量

服务的,它的值可以使任何类型。

说说跟value方法的区别:

  1. constant方法定义的服务不可修改(毕竟是常量)
  2. constant方法定义的服务还可以在 config

函数中被调用

说说constant定义的服务不可修改的原因?

首先得看看模块对象的constant方法的实现:

constant: invokeLater('$provide', 'constant', 'unshift')

不清楚 invokeLater

方法的可以看我上一篇写的《指令》里有介绍。

这里我们要注意的是:数组的unshift方法,从前插入,这就意味着在循环数组时,第一个执行 app.constant()

方法的总是最后一个执行 $provide.constant()

方法,

所以这样就做到了常量的效果,实质上每行代码都执行了,只是值覆盖顺序反过来而已。

说说为什么可以在 config

函数中被调用常量服务?

这就需要看下 $provide.constant

方法的实现:

function constant(name, value) {  assertNotHasOwnProperty(name, 'constant');  providerCache[name] = value; // 因为这句,config函数可以直接注入,并且不需要加后缀Provider  instanceCache[name] = value;}

所以,最终我们可以这样调用服务D:

app    .config(function(D) {        console.log('config: ' + D);    })    .run(function (D) {       console.log(D);    });

6.decorator方法

var app = angular.module('myApp', []);// decoratorapp.config(function ($provide) {    $provide.decorator('A', function ($delegate, $log) {        $delegate.error = function (msg) {            $log.error(msg);        };        return $delegate;   // 注意:一定要返回修改的(或新的)服务实例    });  });decorator

方法不是用来注册服务,而是用来 装饰

现在有服务的(换句话说,就是 扩展

服务或者 重写

服务)。

上面的代码是对服务A进行了扩展,增加了 error

方法。

所以,我们便可以这样调用了:

app    .run(function (A) {       A.error('A decorator') ;    });

另外,需要注意的是,它的调用方式并不是 app.decorator

,而是 $provide.decorator

,因为angular的模块对象并没有提供 decorator

方法。

服务注册的本质上面我们都是使用 app.provider()

, app.factory()

等模块对象的方法注册服务,只有 decorator

例外,用的是 $provide.decorator()

方法。

**我想说的是:真正的服务注册都是调用 $provide

这个服务提供者对应的同名方法。**

其实,之前在《指令》中就说过这个问题,就是 app.directive()

与 $compileProvider.directive()

的关系,不清楚的可以看看。

最后以上就是我对angular服务的理解,如果不对的地方,欢迎留言指正,新浪微博 &8211; Lovesueee。

关键字:AliUED, angular, service, 前端开发


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部