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


当前回答

我对此事的澄清:

基本上,所有提到的类型(服务、工厂、提供者等)都只是创建和配置全局变量(当然,这些变量对整个应用程序是全局的),就像老式的全局变量一样。

虽然不建议使用全局变量,但这些全局变量的实际用途是通过将变量传递给相关控制器来提供依赖注入。

在为“全局变量”创建值时有许多复杂程度:

常数这定义了一个在整个应用程序中不应修改的实际常量,就像其他语言中的常量一样(JavaScript缺少的)。价值这是一个可修改的值或对象,它充当一些全局变量,甚至可以在创建其他服务或工厂时注入(请参阅有关这些的详细信息)。然而,它必须是一个“文字值”,这意味着必须写出实际值,并且不能使用任何计算或编程逻辑(换句话说,39或myText或{prop:“value”}可以,但2+2不行)。工厂一个更一般的值,可以立即计算。它的工作原理是将一个函数传递给AngularJS,其中包含计算值所需的逻辑,AngularJS执行该函数,并将返回值保存在命名变量中。请注意,可以返回对象(在这种情况下,它的功能类似于服务)或函数(将作为回调函数保存在变量中)。服务服务是工厂的更精简版本,只有当值是对象时才有效,它允许直接在函数中写入任何逻辑(就像它是构造函数一样),以及使用this关键字声明和访问对象财产。供应商与工厂的简化版本的服务不同,提供程序是一种更复杂但更灵活的初始化“全局”变量的方法,最大的灵活性是从app.config中设置值的选项。它的工作方式类似于使用服务和提供者的组合,方法是向提供者传递一个函数,该函数具有使用this关键字声明的财产,该关键字可以从app.config中使用。然后它需要有一个单独的$.get函数,该函数由AngularJS在通过app.config文件设置上述财产后执行,该$.get功能的行为与上述工厂相同,因为它的返回值用于初始化“全局”变量。

其他回答

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

另外一个澄清是,工厂可以创建函数/原语,而服务不能。看看这个基于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/

我对此事的澄清:

基本上,所有提到的类型(服务、工厂、提供者等)都只是创建和配置全局变量(当然,这些变量对整个应用程序是全局的),就像老式的全局变量一样。

虽然不建议使用全局变量,但这些全局变量的实际用途是通过将变量传递给相关控制器来提供依赖注入。

在为“全局变量”创建值时有许多复杂程度:

常数这定义了一个在整个应用程序中不应修改的实际常量,就像其他语言中的常量一样(JavaScript缺少的)。价值这是一个可修改的值或对象,它充当一些全局变量,甚至可以在创建其他服务或工厂时注入(请参阅有关这些的详细信息)。然而,它必须是一个“文字值”,这意味着必须写出实际值,并且不能使用任何计算或编程逻辑(换句话说,39或myText或{prop:“value”}可以,但2+2不行)。工厂一个更一般的值,可以立即计算。它的工作原理是将一个函数传递给AngularJS,其中包含计算值所需的逻辑,AngularJS执行该函数,并将返回值保存在命名变量中。请注意,可以返回对象(在这种情况下,它的功能类似于服务)或函数(将作为回调函数保存在变量中)。服务服务是工厂的更精简版本,只有当值是对象时才有效,它允许直接在函数中写入任何逻辑(就像它是构造函数一样),以及使用this关键字声明和访问对象财产。供应商与工厂的简化版本的服务不同,提供程序是一种更复杂但更灵活的初始化“全局”变量的方法,最大的灵活性是从app.config中设置值的选项。它的工作方式类似于使用服务和提供者的组合,方法是向提供者传递一个函数,该函数具有使用this关键字声明的财产,该关键字可以从app.config中使用。然后它需要有一个单独的$.get函数,该函数由AngularJS在通过app.config文件设置上述财产后执行,该$.get功能的行为与上述工厂相同,因为它的返回值用于初始化“全局”变量。

为了澄清这一点,从AngularJS源代码中,您可以看到一个服务只是调用工厂函数,而工厂函数反过来调用提供程序函数:

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

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

看完所有这些帖子后,这给我带来了更多的困惑……但所有的信息都是有价值的。。最后我找到了下表,它将通过简单的比较提供信息

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

对于初学者来说:-这可能不正确的用例,但在高级别上,这是这三个用例的用例。

如果要在angular模块中使用,应将配置函数创建为提供程序

angular.module('myApp').config(函数($testProvider){$testProvider.someFunction();})

Ajax调用或第三方集成需要提供服务。对于数据操作,将其创建为工厂

对于基本场景,工厂和服务的行为相同。