[AngularJS面面观] 23. 依赖注入 --- Factory vs Service

Posted dm_vincent

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[AngularJS面面观] 23. 依赖注入 --- Factory vs Service相关的知识,希望对你有一定的参考价值。

据说99%的angular的初学者都会有一个疑问:factoryservice到底有什么区别?什么情况该用factory,而什么情况又该用service呢?

比如这个Stackoverflow上的这个问题:Service vs Factory,又或者是这个问题:Service vs Provider vs Factory

这些问题都有热心答主回答的很棒了,能够解释清它们共同点,区别以及典型用法。因此本文就换个角度,从最根本的角度来看看这两个概念在源代码层次上是如何实现的。

Factory

首先,我们来看看module上的factory方法,它是创建一个factory的入口方法:

factory: invokeLaterAndSetModuleName('$provide', 'factory')

// 对于factory而言就是调用: invokeLaterAndSetModuleName('$provide', 'factory')
function invokeLaterAndSetModuleName(provider, method) 
  return function(recipeName, factoryFunction) 
    // 在factory函数上设置module名称
    if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;

    // 将factory的定义置入任务队列
    invokeQueue.push([provider, method, arguments]);
    return moduleInstance;
  ;

因此当我们使用该API创建一个factory时,该factory的”蓝图”可以表达成下面这个函数:

// 假设我们创建一个名为aFactory的factory实例:module.factory('aFactory', function())
// 其中的recipeName就是aFactory
function(recipeName, factoryFunction) 
  if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
  invokeQueue.push(['$provide', 'factory', arguments]);
  return moduleInstance;

因此和前面讨论过的providerconstant一样,factory最终也是由$provide.factory方法生产制造出来的(provider对应$provide.providerconstant对应$provide.constant):

function factory(name, factoryFn, enforce) 
  return provider(name, 
    $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
  );

从上面这段代码就可以看出现端倪了。在factory函数的实现内部,它利用了provider实现其功能。如果对于provider有疑问可以先看看这篇文章 - 依赖注入 — Provider是个啥

我们知道provider的目的就是定义依赖应该如何被生成。而定义这个任务的核心就是其内部的$get方法。而factory的目的也是为了定义一个依赖,从目的上而言它和provider的目的不谋而合。因此,factory函数在内部创建了一个符合provider契约的对象,该对象的$get方法实际上就是参数中的factoryFn,而这个factoryFn则是我们定义的。因此调用$get方法就是再调用factoryFn,返回的值也是我们在factoryFn中定义的返回值。

只不过factory函数的第三个参数对具体的$get方法有一点点影响,如果不将它显式地设置成false(必须是false,undefine,null这些都不算),那么就会保证该factory必须是返回值的,否则会抛出异常:

function enforceReturnValue(name, factory) 
  return function enforcedReturnValue() 
    // 实例注入器完成调用
    var result = instanceInjector.invoke(factory, this);
    // 如果返回值为undefined,抛出异常
    if (isUndefined(result)) 
      throw $injectorMinErr('undef', "Provider '0' must return a value from $get factory method.", name);
    
    return result;
  ;

由于我们平常使用factory很少关注第三个参数,因此事实上我们总是确保了factory必须要返回点东西作为其返回值。

分析到这里,factory的实现其实没什么新玩意,算是新瓶装旧酒吧,本质上还是利用了provider来完成其功能。值得注意的地方是,factory必须要返回一个值,由于factory是通过实例注入器来调用的,因此它的返回值最终会被保存到实例注入器的cache中,所以以后不管什么地方通过需要它,拿到的都是同一份实例,也就是说这个factory其实是一个单例对象,在整个angular应用中都只有一份。因此factory中保存的数据是全局唯一的,我们可以把它当作一个用于保存全局数据的地方。

Service

看完了factory的实现,我们再来看看serviceservice这个名字其实挺泛的,什么东西都可以叫做一个servicefactory难道就不是一个service吗?下面是它相关的代码:

// Module API
service: invokeLaterAndSetModuleName('$provide', 'service')

和同样定义在Module API中的factory非常相似,只不过第二个参数从’factory’变成了’service’。这个service指向的函数如下所示:

// Injector内部的service函数
function service(name, constructor) 
  return factory(name, ['$injector', function($injector) 
    return $injector.instantiate(constructor);
  ]);

注意service函数的第二个参数名是constructor,也隐含着它和factory的不同。即我们传入的函数是会被当作一个构造函数来调用的,其内部的实现也证实了这一点:

// 调用的是注入器的instantiate方法,而非invoke方法
return $injector.instantiate(constructor);

另外一个非常非常有意思的地方是,service的内部利用了factory!它实际上还是创建了一个factory,只不过这个factory的任务就是实例化我们定义的constructor

// 内部利用factory完成service的功能
return factory(name, ['$injector', function($injector) 
  return $injector.instantiate(constructor);
]);

在内部创建的factory中,使用基于数组的风格完成了注解的声明(如果不清楚注解是什么,可以先看这篇文章:依赖注入 — 注解的定义与实现)。它依赖于$injector服务,那么这个$injector是指的实例注入器呢,还是provider注入器呢?

首先我们知道servicefactory这一类对象在需要被实例化的时候,实例化它们的请求都是通过实例注入器发起的。当实例注入器发现实例化一个service的时候,还需要首先处理掉它的依赖,也就是上述代码中出现的$injector时,就会首先去实例注入器自身的cache中寻找,发现没有名为”$injector“的对象后,就会去寻求provider注入器的帮助(关于这个流程的具体细节,可以参考依赖注入 — instance注入器以及provider注入器),这个时候就会使用键值”$injectorProvider“去provider注入器中寻找,而恰好在创建注入器的时候就已经定义好这个对象了:

// providerSuffix就是"Provider"
// protoInstanceInjectorj就是实例注入器
providerCache['$injector' + providerSuffix] =  $get: valueFn(protoInstanceInjector) ;

所以最终$injector对应的就是实例注入器,使用的是实例注入器来完成service构造函数的实例化。和factory的一点不同在于,service并不需要定义一个明确的返回值,因为注入器总是会将service的函数当作构造函数创建出一个新对象出来作为依赖。而factory则是自己声明返回的对象作为依赖。

简单总结

Factory和Service是我们经常用的两个API,它们都能够帮助我们定义依赖注入需要的对象。它们的共同点和不同点简单总结如下:

共同点

  1. 创建的对象都是单例对象。这一点是依赖注入机制确保的,因为一旦一个对象被创建完毕了,它总是会被保存到实例注入器的cache中,下次查找这个对象时总是能够从cache中找到它,因此就没有必要再次创建了。
  2. 在底层都创建了一个临时的provider。这一点体现在factory的实现中,而service又在内部依赖于factory。因此它们最终都依赖于这个临时的provider。

不同点

  1. 被注入器调用的方式不同。factory通过$injector.invoke调用;而service则通过$injector.instantiate调用。
  2. 声明函数的调用方式不同。这一点其实和第一点是对应的。factory声明函数被当作一个普通的函数调用,即invoke方式;而service声明函数则是被当作构造函数调用,即instantiate方式。
  3. 是否需要返回值。factory必须要有一个返回值作为依赖对象;service没有这个必要,调用构造函数实例化得到的对象即为依赖对象。

弄清楚了源代码是如何实现factory和service的,再去看看文章开头处提到的那些问题,是不是觉得一切豁然开朗呢?

以上是关于[AngularJS面面观] 23. 依赖注入 --- Factory vs Service的主要内容,如果未能解决你的问题,请参考以下文章

[AngularJS面面观] 22. 依赖注入 --- 配置队列以及运行队列

[AngularJS面面观] 20. 依赖注入 --- instance注入器以及provider注入器

[AngularJS面面观] 24. 依赖注入 --- Value以及Decorator

angularjs广播需要注入吗

AngularJS:依赖注入

AngularJS依赖注入