角度服务与角度工厂

我已经看到angular.factory()angular.service()都用于声明服务;但是,我在官方文档中的任何地方都找不到 angular.service

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

答案

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

直到我以这种方式将自己介绍给自己时,我还是难以解决这个问题:

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

myInjectedService  <----  new myServiceFunction()

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

myInjectedFactory  <---  myFactoryFunction()

您可以根据自己的意愿进行操作,但是有一些有用的模式...

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

function myServiceFunction() {
  this.awesomeApi = function(optional) {
    // calculate some stuff
    return awesomeListOfValues;
  }
}
---------------------------------------------------------------------------------
// Injected in your controller
$scope.awesome = myInjectedService.awesomeApi();

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

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

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

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 可以是对象,函数引用或任何值。例如,如果您编写了一个服务来创建构造函数(如上面的上一个示例中所示),则必须像这样实例化它:

var myShinyNewObject = new myInjectedService.myFunction()

可以说比这更不可取:

var myShinyNewObject = new myInjectedFactory();

(但是您首先应该警惕使用这种类型的模式,因为控制器中的对象会创建难以跟踪的依赖关系,而这些依赖关系很难模拟以进行测试。最好让服务来管理用于以下目的的对象集合而不是 wily-nilly 使用new()


还有一件事,他们都是辛格顿人...

还要记住,在两种情况下,角度都可以帮助您管理单例。无论您在何处或多少次注入服务或功能,都将获得对相同对象或功能的相同引用。 (除了工厂何时仅返回数字或字符串之类的值。在这种情况下,您将始终获得相同的值,而不是引用。)

简单的说 ..

// Service
service = (a, b) => {
  a.lastName = b;
  return a;
};

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

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 的函数引用而返回

用法:对于返回“类”函数很有用,然后可以对其进行新的创建实例。

这是使用 services 和 factory 的示例 。阅读有关AngularJS Service vs Factory 的更多信息。

您也可以查看AngularJS 文档以及关于 stackoverflow 的关于服务与工厂的困惑的类似问题。

TL; DR

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 时 ,Angular 在后台使用'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;
  }
});



非 TL; DR

1)工厂
工厂是创建和配置服务的最流行方法。实际上,TL; DR 所说的不多。您只需创建一个对象,向其添加属性,然后返回该对象即可。然后,当您将工厂传递到控制器中时,对象上的那些属性现在将通过工厂在该控制器中可用。下面是一个更广泛的示例。

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

现在,当我们将 “myFactory” 传递到控制器中时,我们附加到 “服务” 的任何属性都将可用。

现在,让我们在回调函数中添加一些 “私有” 变量。无法从控制器直接访问这些变量,但是我们最终将在 “服务” 上设置一些 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
  • _artist 是我们要查找的艺术家
  • _finalUrl 是我们将呼叫 iTunes 的最终且完全构建的 URL,makeUrl 是可创建并返回 iTunes 友好 URL 的函数。

现在我们的帮助器 / 私有变量和函数已经到位,让我们向 “服务” 对象添加一些属性。无论我们使用什么 “服务”,我们都可以在传递 “myFactory” 的任何控制器中直接使用。

我们将创建仅返回或设置艺术家的 setArtist 和 getArtist 方法。我们还将创建一个方法,该方法将使用创建的 URL 调用 iTunes API。此方法将返回一个诺言,一旦数据从 iTunes API 返回,诺言就会实现。如果您没有太多在 Angular 中使用诺言的经验,我强烈建议您对它们进行深入研究。

setArtist下方,接受一位艺术家,并允许您设置该艺术家。 getArtist返回艺术家 callItunes 首先调用 makeUrl()以便构建我们将用于 $ http 请求的 URL。然后,它设置一个 Promise 对象,使用最终的 URL 发出 $ http 请求,然后由于 $ http 返回 Promise,我们可以在请求后调用. 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” 服务。然后,在 $ scope 对象上设置来自 “myFactory” 数据的属性。上面唯一棘手的代码是,如果您以前从未处理过诺言。由于 callItunes 正在返回承诺,因此我们可以使用. 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 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;
}

现在了解了'new' 关键字在 JavaScript 中的实际作用后,在 Angular 中创建 Service 应该更容易理解。

创建服务时要了解的最大事情是知道服务已使用'new' 关键字实例化。将这些知识与上面的示例相结合,您现在应该认识到,您将把属性和方法直接附加到 “this”,然后将从服务本身返回该属性和方法。让我们看看实际情况。

与我们最初对 Factory 示例所做的不同,我们不需要创建对象然后返回该对象,因为就像之前多次提到的那样,我们使用了'new' 关键字,因此解释器将创建该对象并将其委托给它是原型,然后无需我们完成工作即可将其退还给我们。

首先,让我们创建我们的 “私有” 和帮助函数。这应该看起来非常熟悉,因为我们对工厂进行了完全相同的操作。我不会在这里解释每一行的功能,因为我在工厂示例中做了此操作,如果您感到困惑,请重新阅读工厂示例。

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

});

现在就像在我们的工厂中一样,无论将 myService 传递给哪个控制器,都可以使用 setArtist,getArtist 和 callItunes。这是 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);
      })
  }
});

就像我之前提到的,一旦您真正了解了 “新” 功能,服务就几乎与 Angular 中的工厂相同。

线索就是名字

服务和工厂彼此相似。两者都会产生一个单例对象,可以将其注入其他对象,因此经常互换使用。

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

服务是用于实现服务模式的

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

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

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

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

工厂实施工厂模式

另一方面,工厂旨在实现工厂模式。一种工厂模式,其中我们使用工厂函数来生成对象。通常,我们可能会使用它来构建模型。这是一个返回 Author 构造函数的工厂:

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

我们将这样使用:

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

请注意,工厂也会返回单例。

工厂可以返回构造函数

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

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

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

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

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

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

angular.module('app', [])
  .service('helloService', function() {
    this.sayHello = function() {
      return "Hello!";
    }
  })
  .factory('helloFactory', function() {
    return {
      sayHello: function() {
        return "Hello!";
      }
    }
  });

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

要记住的关键是每个都是另一个的特例。链中的每个特例都使您可以用更少的代码来做相同的事情。每个人也有一些其他限制。

要决定何时使用哪种,您只需看一下哪一个就可以用更少的代码来完成所需的工作。这是一张图片,说明它们之间的相似程度:

在此处输入图片说明

有关逐步使用的详细信息和使用时间的快速参考,可以访问我从以下位置获得此图片的博客文章:

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

app.factory('fn',fn)与 app.service('fn',fn)

施工

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

//factory
 var obj = fn();
 return obj;

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

//service
  var obj = new fn();
  return obj;

实作

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

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” 构造的。

app.service('fn', function () {
         var foo = 0;
         var bar = 0;
         this.setFoo = function (val) {
               foo = val;
         }
         this.setBar = function (val){
               bar = val;
         }
});

结论

在使用工厂或服务时,两者都非常相似。它们被注入到控制器,指令,运行块等中,并以几乎相同的方式在客户端代码中使用。它们也是单例 - 意味着在注入服务 / 工厂的所有位置之间共享同一实例。

那么,您应该选择哪个呢?任一种 - 它们是如此相似,以致差异微不足道。如果您确实选择了一个,请注意它们是如何构造的,以便可以正确实现它们。

我花了一些时间试图找出区别。

我认为工厂函数使用模块模式,服务函数使用标准的 Java 脚本构造函数模式。

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

服务模式恕我直言并没有多大意义,因为它所做的一切都可以像在工厂一样轻松地完成。例外可能是:

  • 如果您出于某种原因关心实例化服务的声明类型 - 如果使用服务模式,则构造函数将是新服务的类型。
  • 如果您已经拥有要在其他地方使用的构造函数,那么您也希望将其用作服务(尽管如果想向其中注入任何东西,可能用处不大!)。

可以说,服务模式是建立从一个语法点的新对象稍微更好的方式,但它也更昂贵实例化。其他人已经指出,angular 使用 “new” 来创建服务,但这并不是真的 - 它无法做到这一点,因为每个服务构造函数都有不同数量的参数。 angular 的实际作用是在内部使用工厂模式来包装构造函数。然后,它会执行一些巧妙的操作来模拟 javascript 的 “新” 运算符,并使用可变数量的可注入参数来调用构造函数 - 但是,如果您直接使用工厂模式,则可以省去此步骤,从而大大提高了效率码。