关于angularjs:angular.service与angular.factory

angular.service vs angular.factory

我见过angular.factory()和angular.service()两种用于声明服务的方法;但是,在官方文档中的任何地方都找不到angular.service

这两种方法有什么区别?哪一个应该用于什么(假设他们做不同的事情)?


1
2
  angular.service('myService', myServiceFunction);
  angular.factory('myFactory', myFactoryFunction);

我很难理解这个概念,直到我把它表达给自己:

服务:您编写的函数将是新的ed:

1
  myInjectedService  <----  new myServiceFunction()

工厂:将调用您编写的函数(构造函数):

1
  myInjectedFactory  <---  myFactoryFunction()

你用它做什么取决于你自己,但是有一些有用的模式…

例如,编写服务函数以公开公共API:

1
2
3
4
5
6
7
8
9
function myServiceFunction() {
  this.awesomeApi = function(optional) {
    // calculate some stuff
    return awesomeListOfValues;
  }
}
---------------------------------------------------------------------------------
// Injected in your controller
$scope.awesome = myInjectedService.awesomeApi();

或者使用工厂函数公开公共API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function myFactoryFunction() {
  var aPrivateVariable ="yay";

  function hello() {
    return"hello mars" + aPrivateVariable;
  }

  // expose a public API
  return {
    hello: hello
  };
}
---------------------------------------------------------------------------------
// Injected in your controller
$scope.hello = myInjectedFactory.hello();

或者使用工厂函数返回构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
function myFactoryFunction() {
    return function() {
        var a = 2;
        this.a2 = function() {
            return a*2;
        };
    };
}
---------------------------------------------------------------------------------
// Injected in your controller
var myShinyNewObject = new myInjectedFactory();
$scope.four = myShinyNewObject.a2();

用哪一个?…

你可以用这两种方法完成相同的事情。但是,在某些情况下,工厂为您提供了一点更大的灵活性来创建具有更简单语法的可注入项。这是因为虽然myInjectedService必须始终是对象,但myInjectedFactory可以是对象、函数引用或任何值。例如,如果您编写了一个服务来创建一个构造函数(如上面的最后一个示例中所示),那么它必须像这样被实例化:

1
var myShinyNewObject = new myInjectedService.myFunction()

比这更不可取的是:

1
var myShinyNewObject = new myInjectedFactory();

(但是您首先应该小心使用这种类型的模式,因为控制器中的新ing对象会创建难以跟踪的依赖项,这些依赖项很难模拟测试。最好让服务为您管理对象集合,而不是随意使用new())。

还有一件事,他们都是单身…

同时要记住,在这两种情况下,Angular都可以帮助您管理一个单例。无论在何处或注入多少次服务或函数,都将获得对同一对象或函数的相同引用。(除非工厂只返回数字或字符串之类的值。在这种情况下,您将始终获得相同的值,但不会得到引用。)


简单地说…

1
2
3
4
5
6
7
8
// Service
service = (a, b) => {
  a.lastName = b;
  return a;
};

// Factory
factory = (a, b) => Object.assign({}, a, { lastName: b });

1
2
3
4
5
6
7
8
9
10
11
12
13
const fullName = { firstName: 'john' };

// Service
const lastNameService = (a, b) => {
  a.lastName = b;
  return a;
};
console.log(lastNameService(fullName, 'doe'));

// Factory
const lastNameFactory = (a, b) =>
  Object.assign({}, a, { lastName: b })
console.log(lastNameFactory(fullName, 'doe'));


主要区别如下:

服务

语法:module.service( 'serviceName', function );

结果:当将servicename声明为可注入参数时,将向您提供传递给module.service的函数的实例。

用法:对于共享实用程序函数很有用,只需将( )附加到注入的函数引用即可调用这些函数。也可以与injectedArg.call( this )或类似产品一起运行。

工厂

语法:module.factory( 'factoryName', function );

结果:将factoryname声明为可注入参数时,将提供通过调用传递给module.factory的函数引用返回的值。

用法:对于返回"class"函数很有用,该函数随后可以被新建以创建实例。

下面是使用服务和工厂的示例。了解更多关于AngularJS服务与工厂的信息。

您还可以查看AngularJS文档以及StackOverflow上的类似问题,这些问题混淆了服务与工厂之间的关系。


DR1)使用工厂时,创建对象,向其添加属性,然后返回同一对象。当您将此工厂传递到控制器中时,对象上的那些属性现在将通过工厂在该控制器中可用。好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
app.controller('myFactoryCtrl', function($scope, myFactory){
  $scope.artist = myFactory.getArtist();
});

app.factory('myFactory', function(){
  var _artist = 'Shakira';
  var service = {};

  service.getArtist = function(){
    return _artist;
  }

  return service;
});

2)使用服务时,Angular会在后台用"new"关键字实例化它。因此,您将向"this"添加属性,服务将返回"this"。当您将服务传递到控制器时,"this"上的那些属性现在将通过您的服务在该控制器上可用。好的。

1
2
3
4
5
6
7
8
9
10
app.controller('myServiceCtrl', function($scope, myService){
  $scope.artist = myService.getArtist();
});

app.service('myService', function(){
  var _artist = 'Nelly';
  this.getArtist = function(){
    return _artist;
  }
});

好的。

非TL好的。

1)工厂工厂是创建和配置服务最流行的方式。"其实没有什么比TL更重要了,"博士说。您只需创建一个对象,向其添加属性,然后返回同一个对象。然后,当您将工厂传递到控制器中时,对象上的这些属性现在将通过工厂在该控制器中可用。下面是一个更广泛的例子。好的。

1
2
3
4
app.factory('myFactory', function(){
  var service = {};
  return service;
});

现在,当我们将"myfactory"传递到控制器中时,我们可以使用附加到"service"的任何属性。好的。

现在,让我们向回调函数添加一些"private"变量。这些不能从控制器直接访问,但我们最终将在"service"上设置一些getter/setter方法,以便在需要时更改这些"private"变量。好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
app.factory('myFactory', function($http, $q){
  var service = {};
  var baseUrl = 'https://itunes.apple.com/search?term=';
  var _artist = '';
  var _finalUrl = '';

  var makeUrl = function(){
   _artist = _artist.split(' ').join('+');
    _finalUrl = baseUrl + _artist + '&callback=JSON_CALLBACK';
    return _finalUrl
  }

  return service;
});

在这里,您会注意到我们没有将这些变量/函数附加到"service"。我们只是创建它们,以便稍后使用或修改它们。好的。

  • base url是iTunes API需要的基本URL
  • _艺术家是我们要查找的艺术家
  • _ final url是最后一个完整构建的url,我们将调用iTunes makeurl,它是一个函数,将创建并返回iTunes友好的URL。

既然我们的helper/private变量和函数已经就位,那么让我们向"service"对象添加一些属性。无论我们在"服务"上做什么,我们都可以直接在任何我们将"我的工厂"传送到的控制器上使用。好的。

我们将创建set artist和getartist方法,这些方法只返回或设置艺术家。我们还将创建一个方法,用我们创建的URL调用iTunes API。这个方法将返回一个承诺,一旦数据从iTunesAPI返回,这个承诺就会实现。如果你没有在角度上使用承诺的经验,我强烈建议你深入研究一下。好的。

下面的set artist接受一个艺术家并允许您设置该艺术家。getartist返回artist callitunes first调用makeurl(),以便构建用于$http请求的URL。然后它设置一个promise对象,用我们的最终URL发出一个$http请求,然后因为$http返回一个promise,我们可以在请求之后调用.success或.error。然后,我们用iTunes数据来解决我们的承诺,或者我们用一条消息拒绝它,说"有一个错误"。好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
app.factory('myFactory', function($http, $q){
  var service = {};
  var baseUrl = 'https://itunes.apple.com/search?term=';
  var _artist = '';
  var _finalUrl = '';

  var makeUrl = function(){
    _artist = _artist.split(' ').join('+');
    _finalUrl = baseUrl + _artist + '&callback=JSON_CALLBACK'
    return _finalUrl;
  }

  service.setArtist = function(artist){
    _artist = artist;
  }

  service.getArtist = function(){
    return _artist;
  }

  service.callItunes = function(){
    makeUrl();
    var deferred = $q.defer();
    $http({
      method: 'JSONP',
      url: _finalUrl
    }).success(function(data){
      deferred.resolve(data);
    }).error(function(){
      deferred.reject('There was an error')
    })
    return deferred.promise;
  }

  return service;
});

现在我们的工厂已经完工了。我们现在可以将"myfactory"注入任何控制器,然后可以调用附加到服务对象的方法(setartist、getartist和callitunes)。好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
app.controller('myFactoryCtrl', function($scope, myFactory){
  $scope.data = {};
  $scope.updateArtist = function(){
    myFactory.setArtist($scope.data.artist);
  };

  $scope.submitArtist = function(){
    myFactory.callItunes()
      .then(function(data){
        $scope.data.artistData = data;
      }, function(data){
        alert(data);
      })
  }
});

在上面的控制器中,我们注入"我的工厂"服务。然后,我们在$scope对象上设置来自"myfactory"的数据的属性。上面唯一棘手的代码是如果你以前从未处理过承诺。因为callitunes返回了一个承诺,所以我们可以使用.then()方法,并且只有在我们的承诺完成之后,才能设置$scope.data.artistdata。你会注意到我们的控制器很薄。我们所有的逻辑和持久数据都位于我们的服务中,而不是在我们的控制器中。2)服务在处理创建服务时,要知道的最大的事情可能是它是用"new"关键字实例化的。对于您的javascript专家来说,这应该给您一个关于代码性质的大提示。对于那些在javascript中背景有限或不太熟悉"new"关键字实际作用的人,让我们回顾一些javascript基本原理,这些基本原理最终将帮助我们了解服务的性质。好的。

要真正了解使用"new"关键字调用函数时所发生的更改,让我们创建一个函数并使用"new"关键字调用它,然后让我们展示解释器在看到"new"关键字时所做的操作。最终结果将是相同的。好的。

首先,让我们创建构造函数。好的。

1
2
3
4
var Person = function(name, age){
  this.name = name;
  this.age = age;
}

这是一个典型的javascript构造函数函数。现在,每当我们使用"new"关键字调用person函数时,"this"都将绑定到新创建的对象。好的。

现在,让我们将一个方法添加到我们人员的原型中,这样它就可以在我们人员"类"的每个实例上使用。好的。

1
2
3
Person.prototype.sayName = function(){
  alert('My name is ' + this.name);
}

现在,因为我们在原型上放置了sayname函数,所以每个人的实例都可以调用sayname函数来警告该实例的名称。好的。

既然我们在原型上有了我们的Person构造函数函数和sayname函数,那么让我们实际创建一个Person实例,然后调用sayname函数。好的。

1
2
var tyler = new Person('Tyler', 23);
tyler.sayName(); //alerts 'My name is Tyler'

因此,所有人共同创建一个个人制造商的代码,将一个功能添加到它的原型,创建一个个人论坛,然后将功能呼叫到它的原型。

okay.ZZU1

现在让我们看看当你在Javascript使用新的关键词时实际发生了什么。首先,你应该注意到,在我们的例子中,我们可以用泰勒的方法(sayname)来称呼它,只要它是一个目标——因为它是一个目标。因此,首先,我们知道,我们的个人制造商回归了一个物件,无论我们是否能看到在代码中。第二,我们知道,由于我们的sayname function被定位在原型上,而不是直接定位在个别的实例上,所以所述的人的功能必须返回到其失败的lookups上的原型上。在更简单的术语中,当我们呼唤Tyler.Sayname()解释者说"好吧,我要看看Tyler Object we just created,locate the Sayname Function,then call it."等一下,我看不见,我只看到名字和年龄,让我检查原型。Yup,looks like it's on the protype,let me call it."

okay.

下面是代码,说明你如何思考新关键词在Javascript的实际使用。这基本上是一个代码的例子。我把解释者的视图或解释者的方式看作是笔记中的代码。

okay.

1
2
3
4
5
6
7
8
9
10
11
12
var Person = function(name, age){
  //The line below this creates an obj object that will delegate to the person's prototype on failed lookups.
  //var obj = Object.create(Person.prototype);

  //The line directly below this sets 'this' to the newly created object
  //this = obj;

  this.name = name;
  this.age = age;

  //return this;
}

现在掌握了新关键词在贾瓦斯克里普的真正知识,在安格拉尔创建一个服务应该很容易理解。

okay.

当创建一个服务时,最重要的事情是知道服务是以新的关键字安装的。将知识与上面的例子结合起来,你现在应该认识到,你将直接把你的性能和方法附在一起,然后从服务中返回。让我们来看看这个行动。

okay.

不象我们原先以工厂为例所做的那样,我们不需要在回复之前创建一个目标,因为我们以前曾多次使用过新的关键词,所以解释者会创建一个目标,即它是原型,然后将它归还给我们而不需要我们做工作。

okay.

首先,让我们创造我们的私人和帮助功能。这应该是非常熟悉的,因为我们和我们的工厂一样。我不想解释这里的每一条线路都是什么因为我在工厂里举了个例子

okay.

1
2
3
4
5
6
7
8
9
10
11
app.service('myService', function($http, $q){
  var baseUrl = 'https://itunes.apple.com/search?term=';
  var _artist = '';
  var _finalUrl = '';

  var makeUrl = function(){
    _artist = _artist.split(' ').join('+');
    _finalUrl = baseUrl + _artist + '&callback=JSON_CALLBACK'
    return _finalUrl;
  }
});

现在,我们将把我们所有的方法都用在我们的控制器上。

okay.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
app.service('myService', function($http, $q){
  var baseUrl = 'https://itunes.apple.com/search?term=';
  var _artist = '';
  var _finalUrl = '';

  var makeUrl = function(){
    _artist = _artist.split(' ').join('+');
    _finalUrl = baseUrl + _artist + '&callback=JSON_CALLBACK'
    return _finalUrl;
  }

  this.setArtist = function(artist){
    _artist = artist;
  }

  this.getArtist = function(){
    return _artist;
  }

  this.callItunes = function(){
    makeUrl();
    var deferred = $q.defer();
    $http({
      method: 'JSONP',
      url: _finalUrl
    }).success(function(data){
      deferred.resolve(data);
    }).error(function(){
      deferred.reject('There was an error')
    })
    return deferred.promise;
  }

});

如今就像在我们的工厂里一样,设计师,Getartist,和Callitunes可以在控制我们进入神秘的时候找到。这里是神秘的控制者(这与我们工厂的控制者完全相同)。

okay.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
app.controller('myServiceCtrl', function($scope, myService){
  $scope.data = {};
  $scope.updateArtist = function(){
    myService.setArtist($scope.data.artist);
  };

  $scope.submitArtist = function(){
    myService.callItunes()
      .then(function(data){
        $scope.data.artistData = data;
      }, function(data){
        alert(data);
      })
  }
});

像上述情况,一旦你真的明白什么是新的,服务是在两个工厂是identical具角。

好。 好的。


线索就在名字里

服务业和工厂彼此相似。两者都会产生一个可以注入到其他对象中的单体对象,因此通常可以互换使用。

它们旨在在语义上用于实现不同的设计模式。

服务用于实现服务模式

服务模式是将应用程序分解为逻辑上一致的功能单元的模式。例如,API访问器或一组业务逻辑。

这在角度上尤其重要,因为角度模型通常只是从服务器中提取的JSON对象,因此我们需要在某个地方放置业务逻辑。

例如,这里有一个GitHub服务。它知道如何与Github交谈。它知道URL和方法。我们可以把它注入控制器,它将生成并返回一个承诺。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(function() {
  var base ="https://api.github.com";

  angular.module('github', [])
    .service('githubService', function( $http ) {
      this.getEvents: function() {
        var url = [
          base,
          '/events',
          '?callback=JSON_CALLBACK'
        ].join('');
        return $http.jsonp(url);
      }
    });
  )();

工厂实施工厂模式

另一方面,工厂旨在实现工厂模式。在其中使用工厂函数生成对象的工厂模式。通常我们可以将其用于构建模型。这是一个返回作者构造函数的工厂:

1
2
3
4
5
angular.module('user', [])
  .factory('User', function($resource) {
    var url = 'http://simple-api.herokuapp.com/api/v1/authors/:id'
    return $resource(url);
  })

我们可以这样利用:

1
2
3
4
angular.module('app', ['user'])
  .controller('authorController', function($scope, User) {
    $scope.user = new User();
  })

请注意,工厂也会退回单件产品。

工厂可以返回构造函数

因为工厂只返回一个对象,所以它可以返回您喜欢的任何类型的对象,包括一个构造函数函数,正如我们上面看到的。

工厂返回一个对象;服务是新的

另一个技术差异是服务和工厂的组成方式。将更新服务函数以生成对象。将调用工厂函数并返回对象。

  • 服务是新的构造函数。
  • 工厂被简单地调用并返回一个对象。

这意味着在服务中,我们附加到"this",在构造函数上下文中,它将指向正在构造的对象。

为了说明这一点,下面是使用服务和工厂创建的相同简单对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
angular.module('app', [])
  .service('helloService', function() {
    this.sayHello = function() {
      return"Hello!";
    }
  })
  .factory('helloFactory', function() {
    return {
      sayHello: function() {
        return"Hello!";
      }
    }
  });


这里所有的答案似乎都是关于服务和工厂的,这是有效的,因为这就是被问到的问题。但也要记住,还有其他几个方面,包括provider()value()constant()

要记住的关键是每一个都是另一个的特殊情况。每一个特殊的情况下,链允许你做同样的事情用更少的代码。每一个都有一些额外的限制。

决定什么时候使用哪一个,你只需看看哪一个允许你用更少的代码做你想做的。这是一张图片,说明它们有多相似:

enter image description here

要获得完整的分步分解和快速参考,您可以访问我从中获取此图片的博客文章:

http://www.simplygoodcode.com/2015/11/the-difference-between-service-provider-and-factory-in-angularjs/


应用工厂("FN",FN)与应用服务("FN",FN)

施工

对于工厂,Angular将调用函数来获取结果。这是缓存和注入的结果。

1
2
3
 //factory
 var obj = fn();
 return obj;

对于服务,Angular将通过调用new来调用构造函数函数。所构造的函数被缓存和注入。

1
2
3
  //service
  var obj = new fn();
  return obj;

实施

工厂通常返回一个对象文本,因为返回值是注入到控制器、运行块、指令等中的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  app.factory('fn', function(){
         var foo = 0;
         var bar = 0;
         function setFoo(val) {
               foo = val;
         }
         function setBar (val){
               bar = val;
         }
         return {
                setFoo: setFoo,
                serBar: setBar
         }
  });

服务函数通常不返回任何内容。相反,它们执行初始化并公开函数。函数也可以引用"this",因为它是使用"new"构造的。

1
2
3
4
5
6
7
8
9
10
app.service('fn', function () {
         var foo = 0;
         var bar = 0;
         this.setFoo = function (val) {
               foo = val;
         }
         this.setBar = function (val){
               bar = val;
         }
});

结论

当涉及到使用工厂或服务时,它们都非常相似。它们被注入到控制器、指令、运行块等中,并以几乎相同的方式用于客户机代码中。它们都是单例的——这意味着在注入服务/工厂的所有地方共享相同的实例。

那么你更喜欢哪一种呢?任何一个-它们是如此相似,以致于差异是微不足道的。如果您确实选择了其中一个而不是另一个,那么只需知道它们是如何构造的,这样您就可以正确地实现它们。


我花了一段时间试图找出其中的区别。

并且我认为工厂功能使用模块模式和服务功能使用标准的Java脚本构造函数模式。


工厂模式更灵活,因为它可以返回函数、值以及对象。

imho的服务模式没有太多意义,因为它所做的一切都可以像处理工厂一样轻松。例外情况可能是:

  • 如果出于某种原因关心实例化服务的声明类型——如果使用服务模式,则构造函数将是新服务的类型。
  • 如果您已经有了一个在其他地方使用的构造函数,并且您还想将其用作服务(如果您想向其中注入任何东西,那么可能不会有太大的用处!).

可以说,从语法的角度来看,服务模式是创建新对象的稍微好一点的方法,但实例化的成本也更高。其他人已经指出Angular使用"new"来创建服务,但这并不完全正确——它无法做到这一点,因为每个服务构造函数都有不同数量的参数。Angular实际做的是在内部使用工厂模式来包装构造函数函数。然后它做了一些巧妙的小动作来模拟javascript的"new"操作符,用可变数量的可注入参数调用构造函数——但是如果直接使用工厂模式,就可以省去这个步骤,从而稍微提高代码的效率。