【问题标题】:What's the point of invokes a constructor with the new operator instead of a factory function使用 new 运算符而不是工厂函数调用构造函数有什么意义
【发布时间】:2014-05-20 21:58:05
【问题描述】:

我正试图弄清楚何时在 angularjs 中使用服务与工厂。

这是我喜欢的答案,但我有一部分感到困惑

https://stackoverflow.com/a/20761653/663447

服务配方与工厂配方几乎相同,但在这里 Injector 使用 new 运算符调用构造函数,而不是 工厂函数。

这是什么意思?为什么我想要一个带有 new 运算符而不是工厂函数的构造函数。

谢谢。

【问题讨论】:

    标签: javascript angularjs factory


    【解决方案1】:

    这是我之前给出的一个很长的答案。它非常详细地解释了使用“新”关键字与不使用它的原因。

    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 中使用 Promise 的经验,我强烈建议你深入研究它们。

    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 返回了一个 Promise,所以我们可以使用 .then() 方法,并且只有在我们的 Promise 与 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 中创建服务应该更容易理解。

    创建服务时要了解的最重要的事情是知道服务是使用“新”关键字实例化的。将这些知识与我们上面的示例相结合,您现在应该认识到您将直接将属性和方法附加到“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;
      }
    
    });
    

    现在就像在我们的工厂中一样,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);
          })
      }
    });
    

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

    【讨论】:

    • 如果您已经在 SO 上发布了此内容,您可能只想将链接作为关闭的理由,因为它已经得到了回答……不过谢谢。
    • 所以我有这个问题,所以我研究了它并找到了这篇文章。 meta.stackexchange.com/questions/113385/… 基本上说如果它回答了手头的问题,那很好。但是,如果规则/准则发生了变化,我可以将其删除。
    • 是的,对我来说没什么大不了的,只是想如果问题有 95% 重叠并且两者的答案相同,那么问题可能会更好,只是为了保持 SO 尽可能小/尽可能专注。答案很好,所以你的电话。
    【解决方案2】:

    这是什么意思?为什么我想要一个带有 new 运算符而不是工厂函数的构造函数。

    因为构造函数将原型与它们生成的对象相关联。工厂函数可以 做到这一点(通过在幕后调用构造函数,或使用 Object.create 在幕后),但如果你有一个构造函数,它完全可以满足你的需求想要,嗯,能用就好了。

    让我们从 Angular 退一步,看一个简单的例子:

    function Foo() {
        this.answer = 42;
    }
    

    几乎可以肯定,它被设计为构造函数。那就是:

    var f = new Foo();
    console.log(f.answer); // 42
    

    如果没有new,我们会得到一个错误:

    var f = Foo();
    console.log(f.answer); // TypeError: Cannot read property 'answer' of undefined
    

    ...因为Foo 没有返回值。

    原型是这样进入的:

    function Foo() {
    }
    Foo.prototype.speak = function() {
        alert("I'm a Foo");
    };
    var f = new Foo();
    f.speak(); // alerts "I'm a Foo"
    

    ...再说一次,如果您没有使用new,您在尝试访问f.speak 时会出错。

    工厂函数可以创建由原型支持的对象,但它需要一个(微不足道的)额外步骤。他们只是做得很好,所以如果你有一个构造函数,你可以直接使用它。

    【讨论】:

      【解决方案3】:

      基本上它减少了您需要编写的代码量。如果在对象上调用 new ,您可以使用它来定义构造函数中的函数,如果您只是调用该函数,则由您来创建一个对象并返回它。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-07-29
        • 1970-01-01
        • 2010-10-25
        • 2014-06-16
        • 1970-01-01
        • 2011-05-21
        • 2016-01-31
        相关资源
        最近更新 更多