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


当前回答

对于新手来说,这是一个非常令人困惑的部分,我试图用简单的语言来解释它

AngularJS服务:用于与控制器中的服务引用共享实用程序函数。服务本质上是单例的,因此对于一个服务,浏览器中只创建一个实例,并且在整个页面中使用相同的引用。

在服务中,我们使用此对象创建函数名作为属性。

AngularJS Factory:Factory的用途也与Service相同,但在本例中,我们创建了一个新对象,并添加函数作为该对象的财产,最后返回该对象。

AngularJS Provider:其目的也是相同的,不过Provider提供了$get函数的输出。

定义和使用服务、工厂和提供商在http://www.dotnetfunda.com/articles/show/3156/difference-between-angularjs-service-factory-and-provider

其他回答

JS Fiddle演示

工厂/服务/供应商的“Hello world”示例:

var myApp=角度模块('myApp',[]);//服务风格,可能是最简单的myApp.service('helloWorldFromService',函数(){this.sayHello=函数(){return“你好,世界!”;};});//工厂风格,更加复杂myApp.factory('helloWorldFromFactory',函数(){返回{sayHello:函数(){return“你好,世界!”;}};});//提供程序风格、全面、可配置版本myApp.provider('helloWorld',函数(){this.name='默认值';这一点$get=函数(){var名称=this.name;返回{sayHello:函数(){return“您好,”+name+“!”;}}};this.setName=函数(名称){this.name=名称;};});//嘿,我们可以配置提供者!myApp.config(函数(helloWorldProvider){helloWorldProvider.setName('World');});函数MyCtrl($scope、helloWorld、helloWorldFromFactory、helloWorldFromService){$scope.hellos=[helloWorld.sayHello(),helloWorldFromFactory.sayHello(),helloWorldFromService.sayHello()];}<script src=“https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js“></script><body ng app=“myApp”><div ng controller=“MyCtrl”>{{hellos}}</div></body>

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

句法糖是区别。只需要提供程序。或者换句话说,只有提供者才是真正的角度,所有其他提供者都是派生的(以减少代码)。还有一个简单的版本,叫做Value(),它只返回值,不返回计算或函数。偶数值是从提供程序派生的!

那么,为什么会出现这样的并发症,为什么我们不能只使用提供者而忘记其他一切呢?它应该可以帮助我们轻松编写代码,更好地进行沟通。开玩笑的回答是,它越复杂,框架的销售就越好。


可以返回value=value的提供程序一个可以实例化并返回=工厂(+值)提供程序可以实例化+做某事=服务(+工厂,+值)提供者=必须包含名为$get(+Factory,+Service,+Value)的属性

角注入为我们得出这一结论提供了第一个线索。

$injector用于检索提供者“不是服务,不是工厂,而是提供者”定义的对象实例。

更好的答案是:Angular服务由服务工厂创建。这些服务工厂是由服务提供程序创建的函数。服务提供程序是构造函数。实例化时,它们必须包含名为$get的属性,该属性保存服务工厂函数

因此,主供应器和注入器都将就位:)。当$get可以通过从IServiceProvider继承而在提供程序中实现时,Typescript就变得有趣了。

我对此事的澄清:

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

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

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

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

我的理解很简单。

工厂:您只需在工厂内部创建一个对象并返回它。

服务:

您只需要一个使用此关键字定义函数的标准函数。

供应商:

您定义了一个$get对象,它可以用于获取返回数据的对象。