AngularJS中的服务、提供商和工厂之间有什么区别?


当前回答

从AngularJS邮件列表中,我得到了一个令人惊叹的线程,它解释了服务与工厂与供应商以及它们的注入用法。编写答案:

服务

语法:module.service('serviceName',function);结果:当将serviceName声明为可注入参数时,将为您提供函数的实例。换句话说,新的FunctionYouPassedToService()。

工厂

语法:module.factory('factoryName',function);结果:当将factoryName声明为可注入参数时,将提供通过调用传递给module.factory的函数引用返回的值。

提供商

语法:module.provider('providerName',function);结果:当将providerName声明为可注入参数时,将提供(newProviderFunction())$get()。在调用$get方法之前实例化构造函数函数-ProviderFunction是传递给module.provider的函数引用。

提供程序的优点是可以在模块配置阶段进行配置。

请参阅此处获取提供的代码。

下面是Misko的进一步解释:

provide.value('a', 123);

function Controller(a) {
  expect(a).toEqual(123);
}

在这种情况下,注入器只是按原样返回值。但是如果您想计算该值呢?然后使用工厂

provide.factory('b', function(a) {
  return a*2;
});

function Controller(b) {
  expect(b).toEqual(246);
}

因此,工厂是一个负责创造价值的职能部门。注意,工厂函数可以要求其他依赖项。

但是,如果你想成为更多的OO,并拥有一个名为Greeter的类呢?

function Greeter(a) {
  this.greet = function() {
    return 'Hello ' + a;
  }
}

然后,要实例化,必须编写

provide.factory('greeter', function(a) {
  return new Greeter(a);
});

然后我们可以像这样在控制器中请求“问候者”

function Controller(greeter) {
  expect(greeter instanceof Greeter).toBe(true);
  expect(greeter.greet()).toEqual('Hello 123');
}

但这太啰嗦了。一个简短的写法是provider.service(“greeter”,greeter);

但是如果我们想在注入之前配置Greeter类呢?然后我们可以写

provide.provider('greeter2', function() {
  var salutation = 'Hello';
  this.setSalutation = function(s) {
    salutation = s;
  }

  function Greeter(a) {
    this.greet = function() {
      return salutation + ' ' + a;
    }
  }

  this.$get = function(a) {
    return new Greeter(a);
  };
});

然后我们可以这样做:

angular.module('abc', []).config(function(greeter2Provider) {
  greeter2Provider.setSalutation('Halo');
});

function Controller(greeter2) {
  expect(greeter2.greet()).toEqual('Halo 123');
}

顺便说一句,服务、工厂和价值都来自提供者。

provider.service = function(name, Class) {
  provider.provide(name, function() {
    this.$get = function($injector) {
      return $injector.instantiate(Class);
    };
  });
}

provider.factory = function(name, factory) {
  provider.provide(name, function() {
    this.$get = function($injector) {
      return $injector.invoke(factory);
    };
  });
}

provider.value = function(name, value) {
  provider.factory(name, function() {
    return value;
  });
};

其他回答

另外一个澄清是,工厂可以创建函数/原语,而服务不能。看看这个基于Epokk的jsFiddle:http://jsfiddle.net/skeller88/PxdSP/1351/.

工厂返回一个可以调用的函数:

myApp.factory('helloWorldFromFactory', function() {
  return function() {
    return "Hello, World!";
  };
});

工厂还可以返回具有可调用方法的对象:

myApp.factory('helloWorldFromFactory', function() {
  return {
    sayHello: function() {
      return "Hello, World!";
    }
  };
});

该服务返回一个具有可调用方法的对象:

myApp.service('helloWorldFromService', function() {
  this.sayHello = function() {
     return "Hello, World!";
  };
});

有关详细信息,请参阅我写的关于差异的帖子:http://www.shanemkeller.com/tldr-services-vs-factories-in-angular/

Angular文档摘要:

有五种配方类型定义了如何创建对象:Value,工厂、服务、供应商和常量。工厂和服务是最常用的配方。它们之间的唯一区别是,Service配方对自定义类型的对象更有效,而Factory可以生成JavaScript原语和函数。提供者配方是核心配方类型,所有其他配方都只是其语法上的糖。提供程序是最复杂的配方类型。除非您正在构建一段需要全局配置的可重用代码,否则您不需要它。


SO的最佳答案:

https://stackoverflow.com/a/26924234/165673(<--良好)https://stackoverflow.com/a/27263882/165673https://stackoverflow.com/a/16566144/165673

此答案针对主题/问题

Factory、Service和Constant是如何在提供者配方之上添加语法糖的?

OR

工厂、服务和供应商在内部是怎样的

基本上发生的是

当您创建factory()时,它会将第二个参数中提供的函数设置为provider的$get并返回它(provider(name,{$get:factoryFn})),您所得到的只是provider,但除了$get之外没有其他属性/方法(这意味着您无法配置)

工厂源代码

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

当生成服务()时,它返回您提供factory()的函数,该函数注入构造函数(返回您在服务中提供的构造函数的实例)并返回它

服务源代码

function service(name, constructor) {
    return factory(name, ['$injector', function($injector) {
      return $injector.instantiate(constructor);
    }]);
};

因此,基本上在这两种情况下,您最终都会将providers$get设置为您提供的函数,但您可以提供$get以外的任何内容,因为您最初可以在provider()中为config块提供

TL;博士1) 使用Factory时,您可以创建一个对象,向其添加财产,然后返回相同的对象。当您将此工厂传递到控制器中时,对象上的那些财产现在将通过工厂在该控制器中可用。

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) 当您使用Service时,AngularJS会在后台使用“new”关键字实例化它。因此,您将财产添加到“this”,服务将返回“this“。当您将服务传递到控制器时,“this”上的那些财产现在将通过您的服务在该控制器上可用。

app.controller(‘myServiceCtrl’, function($scope, myService){
  $scope.artist = myService.getArtist();
});

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

3) 提供程序是唯一可以传递给.config()函数的服务。如果要在服务对象可用之前为其提供模块范围的配置,请使用提供程序。

app.controller(‘myProvider’, function($scope, myProvider){
  $scope.artist = myProvider.getArtist();
  $scope.data.thingFromConfig = myProvider.thingOnConfig;
});

app.provider(‘myProvider’, function(){
 //Only the next two lines are available in the app.config()
 this._artist = ‘’;
 this.thingFromConfig = ‘’;
  this.$get = function(){
    var that = this;
    return {
      getArtist: function(){
        return that._artist;
      },
      thingOnConfig: that.thingFromConfig
    }
  }
});

app.config(function(myProviderProvider){
  myProviderProvider.thingFromConfig = ‘This was set in config’;
});

非TL;博士

1) 工厂工厂是创建和配置服务的最流行方式。除了TL之外,真的没有太多了;DR说。您只需创建一个对象,向其添加财产,然后返回相同的对象。然后,当您将工厂传递到控制器中时,对象上的那些财产现在将通过工厂在该控制器中可用。下面是一个更广泛的示例。

app.factory(‘myFactory’, function(){
  var service = {};
  return service;
});

现在,当我们将“myFactory”传递到我们的控制器时,我们附加到“service”的任何财产都将可用。

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

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;
});

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

baseUrl是iTunes API所需的基本URL_艺术家是我们希望查找的艺术家_finalUrl是我们将调用iTunes的最终和完全构建的URLmakeUrl是一个创建并返回iTunes友好URL的函数。

既然我们的helper/private变量和函数已经就位,那么让我们向“service”对象添加一些财产。无论我们在“服务”上放置什么,都可以直接在我们传递“myFactory”到的任何控制器中使用。

我们将创建setArtist和getArtist方法,它们只是返回或设置艺术家。我们还将创建一个方法,用我们创建的URL调用iTunes API。该方法将返回一个承诺,一旦数据从iTunes API返回,该承诺将实现。如果你在AngularJS中没有太多使用承诺的经验,我强烈建议你深入研究一下。

下面的setArtist接受艺术家并允许您设置艺术家。getArtist返回艺术家。callItunes首先调用makeUrl()来构建我们将用于$http请求的URL。然后它设置一个promise对象,用我们的最终url发出一个$http请求,然后因为$http返回一个promice,所以我们可以在请求后调用.success或.error。然后,我们用iTunes数据来解决我们的承诺,或者用一条消息“出现了错误”来拒绝它。

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)。

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);
      })
  }
});

在上面的控制器中,我们正在注入“myFactory”服务。然后,我们使用“myFactory”中的数据在$scope对象上设置财产。上面唯一棘手的代码是,如果你以前从未处理过承诺。因为callItunes正在返回一个promise,所以我们可以使用.then()方法,并在iTunes数据实现我们的承诺后,才设置$scope.data.artistData。你会注意到我们的控制器非常“薄”(这是一个很好的编码练习)。我们所有的逻辑和持久数据都位于我们的服务中,而不是控制器中。2) 服务在处理创建服务时,最重要的一点可能是它是用“new”关键字实例化的。对于JavaScript专家来说,这应该会给你一个关于代码本质的重要提示。对于那些JavaScript背景有限的人,或者对于那些不太熟悉“new”关键字实际功能的人,让我们回顾一些JavaScript基础知识,这些基础知识最终将帮助我们理解服务的本质。

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

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

var Person = function(name, age){
  this.name = name;
  this.age = age;
}

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

现在,让我们将一个方法添加到Person的原型中,以便它可以在Person“类”的每个实例上使用。

Person.prototype.sayName = function(){
  alert(‘My name is ‘ + this.name);
}

现在,因为我们将sayName函数放在原型上,Person的每个实例都可以调用sayName功能,以提醒该实例的名称。

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

var tyler = new Person(‘Tyler’, 23);
tyler.sayName(); //alerts ‘My name is Tyler’

因此,创建Person构造函数、向其原型添加函数、创建Person实例,然后在其原型上调用函数的代码看起来都是这样的。

var Person = function(name, age){
  this.name = name;
  this.age = age;
}
Person.prototype.sayName = function(){
  alert(‘My name is ‘ + this.name);
}
var tyler = new Person(‘Tyler’, 23);
tyler.sayName(); //alerts ‘My name is Tyler’

现在让我们看看在JavaScript中使用“new”关键字时实际发生了什么。首先您应该注意的是,在我们的示例中使用了“new”之后,我们能够像调用对象一样调用“tyler”上的方法(sayName),这是因为它是一个对象。因此,首先,我们知道Person构造函数正在返回一个对象,无论我们是否可以在代码中看到它。其次,我们知道,因为sayName函数位于原型上,而不是直接位于Person实例上,所以Person函数返回的对象必须在查找失败时委托给原型。更简单地说,当我们调用tyler.sayName()时,解释器会说“好的,我要查看我们刚刚创建的‘tyler’对象,找到sayName函数,然后调用它。等一下,我在这里看不到它-我看到的只是名称和年龄,让我检查一下原型。是的,看起来它在原型上,让我调用它”。

下面是如何思考“new”关键字在JavaScript中的实际作用的代码。这基本上是上面一段的代码示例。我已经将“解释器视图”或解释器在注释中看到代码的方式。

var Person = function(name, age){
  //The below line creates an object(obj) 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;
}

现在了解了“new”关键字在JavaScript中的真正作用,在AngularJS中创建服务应该更容易理解。

创建服务时最需要理解的是知道服务是用“new”关键字实例化的。结合上述知识和我们的示例,您现在应该认识到,您将把财产和方法直接附加到“this”,然后它将从服务本身返回。让我们来看看这一点。

与我们最初在Factory示例中所做的不同,我们不需要创建一个对象然后返回该对象,因为正如前面多次提到的那样,我们使用了“new”关键字,因此解释器将创建该对象,将其委托给其原型,然后将其返回给我们,而无需我们做任何工作。

首先,让我们创建“private”和helper函数。这看起来应该很熟悉,因为我们对工厂做了完全相同的事情。我不会在这里解释每一行的作用,因为我在工厂示例中做过,如果你感到困惑,请重新阅读工厂示例。

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”。

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;
  }

});

现在,就像在我们的工厂中一样,setArtist、getArtist和callItunes将在我们传递myService的任何控制器中可用。这是myService控制器(与我们的工厂控制器几乎完全相同)。

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);
      })
  }
});

正如我之前提到的,一旦你真正了解了“新”的作用,服务几乎与AngularJS中的工厂完全相同。3) 提供商

关于提供程序,最重要的一点是它们是您可以传递到应用程序的app.config部分的唯一服务。如果您需要在服务对象的某一部分在应用程序中的其他地方可用之前对其进行更改,这一点非常重要。虽然与服务/工厂非常相似,但我们将讨论一些差异。

首先,我们以与我们的服务和工厂类似的方式建立我们的提供商。下面的变量是我们的“private”和helper函数。

app.provider('myProvider', function(){
   var baseUrl = 'https://itunes.apple.com/search?term=';
  var _artist = '';
  var _finalUrl = '';

  //Going to set this property on the config function below.
  this.thingFromConfig = ‘’;

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

*同样,如果上述代码的任何部分令人困惑,请查看Factory部分,在那里我将更详细地解释它的作用。

您可以将提供商视为有三个部分。第一部分是稍后将修改/设置的“私有”变量/函数(如上所示)。第二部分是变量/函数,这些变量/函数将在app.config函数中可用,因此可以在其他地方可用之前进行更改(如上所示)。需要注意的是,这些变量需要附加到“this”关键字。在我们的示例中,app.config中只有“thingFromConfig”可以更改。第三部分(如下所示)是当您将“myProvider”服务传递到特定控制器中时,控制器中可用的所有变量/函数。

使用Provider创建服务时,控制器中唯一可用的财产/方法是从$get()函数返回的财产/方法。下面的代码将$get放在“this”上(我们知道最终会从该函数返回)。现在,$get函数返回我们希望在控制器中可用的所有方法/财产。下面是一个代码示例。

this.$get = function($http, $q){
    return {
      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;
      },
      setArtist: function(artist){
        _artist = artist;
      },
      getArtist: function(){
        return _artist;
      },
      thingOnConfig: this.thingFromConfig
    }
  }

现在完整的Provider代码如下所示

app.provider('myProvider', function(){
  var baseUrl = 'https://itunes.apple.com/search?term=';
  var _artist = '';
  var _finalUrl = '';

  //Going to set this property on the config function below
  this.thingFromConfig = '';

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

  this.$get = function($http, $q){
    return {
      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;
      },
      setArtist: function(artist){
        _artist = artist;
      },
      getArtist: function(){
        return _artist;
      },
      thingOnConfig: this.thingFromConfig
    }
  }
});

现在,就像在我们的工厂和服务中一样,setArtist、getArtist和callItunes将在我们传递给myProvider的任何控制器中可用。这是myProvider控制器(与我们的工厂/服务控制器几乎完全相同)。

app.controller('myProviderCtrl', function($scope, myProvider){
  $scope.data = {};
  $scope.updateArtist = function(){
    myProvider.setArtist($scope.data.artist);
  };

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

  $scope.data.thingFromConfig = myProvider.thingOnConfig;
});

如前所述,使用Provider创建服务的关键是能够在将最终对象传递给应用程序的其余部分之前,通过app.config函数更改一些变量。让我们看一个例子。

app.config(function(myProviderProvider){
  //Providers are the only service you can pass into app.config
  myProviderProvider.thingFromConfig = 'This sentence was set in app.config. Providers are the only service that can be passed into config. Check out the code to see how it works';
});

现在您可以看到“thingFromConfig”在我们的提供程序中是如何作为空字符串的,但当它显示在DOM中时,它将是“This sentence was set…”。

所有服务均为单人服务;每个应用程序实例化一次。它们可以是任何类型,无论是原语、对象文本、函数,甚至是自定义类型的实例。

值、工厂、服务、常量和提供程序方法都是提供程序。他们教Injector如何实例化服务。

最冗长,但也是最全面的是提供者配方其余四种配方类型-价值、工厂、服务和常量-只是提供程序配方之上的语法糖。

Value Recipe是最简单的例子,您自己实例化服务,并将实例化的值提供给注入器。Factory配方为Injector提供了一个工厂函数,当它需要实例化服务时调用该函数。调用时,工厂函数创建并返回服务实例。服务的依赖项作为函数的参数注入。因此,使用此配方可增加以下功能:能够使用其他服务(具有依赖关系)服务初始化延迟/延迟初始化Service配方与Factory配方几乎相同,但这里Injector调用带有新运算符的构造函数,而不是工厂函数。提供商的配方通常是过度的。它通过允许您配置工厂的创建,增加了一个间接层。

只有当您想要公开API时,才应该使用Provider配方对于必须在应用程序启动。这通常只对可重复使用有用其行为可能需要在应用。常量配方与值配方类似,只是它允许您定义在配置阶段可用的服务。比使用Value配方创建的服务更快。与Values不同,它们不能使用decorator进行装饰。请参阅提供程序文档。