【问题标题】:Prototypal inheritance using Angularjs module.service使用 Angularjs module.service 的原型继承
【发布时间】:2016-03-10 19:06:28
【问题描述】:

我已经使用 Angularjs 工作了几个月,我想知道如何使用这个框架实现高效的 OOP。

我正在一个项目中工作,我需要实例化一个“终端”类,该类具有构造函数属性和一系列方法(作为普通类)。在普通 JS 中,我会使用伪经典模式(将构造函数属性与原型链中的方法分开)。

根据我对 Angularjs 的了解,model.service() 将是最佳选择,因为我每次调用它时都会创建一个新的服务实例。但是在定义方法时,我通常会看到下一个实现:

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

但是,它不会在我每次调用类时创建函数 sayHello() 吗?我想知道将功能分开是否会更好,例如:

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

helloWorldFromService.prototype.showMessage = function() {
    return this.msg;
};

这样,函数 showMessage() 将只在内存中创建一次,并在创建的服务的所有实例之间共享。

另外,如果这是可能的(并且如果它确实使代码更高效),那么实现它的方法是什么? (上面的代码只是一个猜测)

谢谢

【问题讨论】:

  • “每次我打电话给班级” ...不,服务是单例的。要执行第二个版本,您需要将命名函数引用传递给服务而不是匿名函数
  • 你说得对,我不太了解服务的目的。看看我对 rob 的回答的评论

标签: javascript angularjs oop angularjs-service


【解决方案1】:

根据评论进行编辑:看起来如果您只需返回对象的构造函数即可执行此操作,然后通过调用服务访问原型。

快速说明,在示例中,单击“再次获取”将调用原型函数 changeHelloWorldString 并使用控件名称更新字符串。单击“更改原型”后,该 changeHelloWorldString 函数将更改为将“ [PROTOTYPE CHANGE]”附加到字符串。单击任一“再次获取”按钮将证明在第二个控制器中所做的原型更改影响了两个控制器中对象的原型链。

请看下面的例子:

angular.module('myModule2', [])
  .factory('myService', function() {
    function FactoryConstructor(thirdFunction) {
      this.helloWorldFunction = function() {
        return this.helloWorldString;
      }
      this.thirdFunction = thirdFunction;
    }
    FactoryConstructor.prototype.helloWorldString = 'Hello World';
    FactoryConstructor.prototype.changeHelloWorldString = function(newString) {
      this.helloWorldString = newString;
    };
    FactoryConstructor.prototype.changeThirdFunction = function(newFunction) {
      this.thirdFunction = newFunction;
    }
    return FactoryConstructor;
  })
  .controller('ctrl1', function($scope, myService) {
    var factoryResult = new myService(function() {
      this.helloWorldString += ' first';
    });
    $scope.hwString = factoryResult.helloWorldString;
    $scope.hwString2 = factoryResult.helloWorldFunction();
    // console.log(factoryResult instanceof myService) //tested true
    $scope.getAgain = function() {
      factoryResult.changeHelloWorldString('ctrl1 String');
      factoryResult.thirdFunction();
      $scope.hwString = factoryResult.helloWorldString;
      $scope.hwString2 = factoryResult.helloWorldFunction();
    }
  })
  .controller('ctrl2', function($scope, myService) {
    var factoryResult = new myService(function() {
      this.helloWorldString += ' second';
    });
    $scope.hwString = factoryResult.helloWorldString;
    $scope.hwString2 = factoryResult.helloWorldFunction();
    // console.log(factoryResult instanceof myService) //tested true
    $scope.getAgain = function() {
      factoryResult.changeHelloWorldString('ctrl2 String');
      factoryResult.thirdFunction();
      factoryResult.changeThirdFunction(function() {
        this.helloWorldString += ' third';
      });
      $scope.hwString = factoryResult.helloWorldString;
      $scope.hwString2 = factoryResult.helloWorldFunction();
    }
    
    $scope.changePrototype = function() {
      myService.prototype.changeHelloWorldString = function(newString) {
        this.helloWorldString = newString + " [PROTOTYPE CHANGE]";
      }
    }
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='myModule2'>
  <div ng-controller='ctrl1'>
    <div>{{hwString}}</div>
    <div>{{hwString2}}</div>
    <button ng-click='getAgain()'>Get again</button>
  </div>
  <div ng-controller='ctrl2'>
    <div>{{hwString}}</div>
    <div>{{hwString2}}</div>
    <button ng-click='getAgain()'>Get again</button>
    <button ng-click='changePrototype()'>Change Prototype</button>
  </div>
</div>

this answer 中可以找到另一个很好的解释。这可能通过使用提供者(服务和工厂都派生自,请参阅作者的旁注)来显示更好/更清洁的方式来执行本示例所示的操作。

以下原始帖子的其余部分为背景

Angular 服务是可以注入到许多地方的单例。 所以如果你这样做:

angular.module('myModule', [])
.service('myService', function() {
  var myService = this;
  this.helloWorldString = 'Hello World String';
  this.helloWorldFunction = function() {
    return myService.helloWorldString;
  }
})
.controller('main', function($scope, myService) {
  $scope.getAgain = function() {
    $scope.hwString = myService.helloWorldString;
    $scope.hwString2 = myService.helloWorldFunction();
  }
  $scope.getAgain();
})
.controller('notMain', function($scope, myService) {
  myService.helloWorldString = 'edited Hello World String';
  $scope.hwString = myService.helloWorldString;
  $scope.hwString2 = myService.helloWorldFunction();
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='myModule'>
  <div ng-controller='main'>
    <div>{{hwString}}</div>
    <div>{{hwString2}}</div>
    <button ng-click='getAgain()'>Get again</button>
  </div>
  <div ng-controller='notMain'>
    <div>{{hwString}}</div>
    <div>{{hwString2}}</div>
  </div>
</div>

您会注意到最初两者是不同的,因为第一对是在更改之前获得的,但在第二个控制器中所做的更改确实会影响第一个。只需单击“再次获取”按钮,它就会从服务中重新提取信息并且现在匹配,证明它们是同一个对象,尽管它们被注入到两个不同的控制器中。

看起来你真正想要的是一个工厂(虽然这主要是语义,但你可以在下一个示例中将“工厂”更改为“服务”,它会产生相同的结果。这也可以在 Angular 中看到文档本身。documentation for a service 从不实际使用 .service,它始终使用 .factory)。这样,您实际上可以在调用时构造工厂对象的新实例,在本例中为“myService(...)”。使用这些函数参数,您可以自定义返回对象的属性,包括函数,如示例中所示。

angular.module('myModule2', [])
  .factory('myService', function() {
    return function(stringInput, thirdFunction) {
      return {
        helloWorldString: stringInput,
        helloWorldFunction: function() {
          return this.helloWorldString;
        },
        thirdFunction: thirdFunction
      }
    }
  })
  .controller('ctrl1', function($scope, myService) {
    var factoryResult = myService('Hello World String', function () {
      this.helloWorldString += ' first';
    });
    $scope.hwString = factoryResult.helloWorldString;
    $scope.hwString2 = factoryResult.helloWorldFunction();
  
    $scope.getAgain = function() {
      factoryResult.thirdFunction();
      $scope.hwString = factoryResult.helloWorldString;
      $scope.hwString2 = factoryResult.helloWorldFunction();
    }
  })
  .controller('ctrl2', function($scope, myService) {
    var factoryResult = myService('new Hello World String', function () {
      this.helloWorldString += ' second';
    });
    $scope.hwString = factoryResult.helloWorldString;
    $scope.hwString2 = factoryResult.helloWorldFunction();
  
    $scope.getAgain = function() {
      factoryResult.thirdFunction();
      $scope.hwString = factoryResult.helloWorldString;
      $scope.hwString2 = factoryResult.helloWorldFunction();
    }
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='myModule2'>
  <div ng-controller='ctrl1'>
    <div>{{hwString}}</div>
    <div>{{hwString2}}</div>
    <button ng-click='getAgain()'>Get again</button>
  </div>
  <div ng-controller='ctrl2'>
    <div>{{hwString}}</div>
    <div>{{hwString2}}</div>
    <button ng-click='getAgain()'>Get again</button>
  </div>
</div>

【讨论】:

  • 是的,这就是我要找的。我想我没有很好地理解它们之间的区别。但是现在我对这种方法提出了同样的问题:¿它不会创建函数helloWorldFunction 两次,每个实例创建一个吗?没有办法为这些功能使用原型继承吗?谢谢
  • 我明白你在说什么。我会玩弄这个。我有一个想法,你可以如何以更 Angular 的方式做到这一点。一旦有机会测试,我会进行编辑。
  • @GabrielRequenaGarcía 这应该以您希望的方式回答问题。如果您需要更多,请告诉我。这里真正的诀窍是 Angular 仍然是 Javascript。有时,因为这里经常用 JS 和那里的 Angular 来讨论它,所以我们可以忘记这一点。服务只是返回一个 Javascript 对象,但它们是作为单例返回的,这就是本示例有效的原因。 “myService”服务返回一个 singleton 构造函数,它允许您实例化它,并通过它有效地访问原型链。
  • @GabrielRequenaGarcía 事实上,这个问题的另一个解决方案(非Angular-y)是简单地添加另一个具有构造函数的JS文件。然后,您可以从 Angular 代码中访问此文件。这与您可以包含下划线库并在整个 Angular 代码中访问它的方式相同。但所提出的解决方案是您可以在 Angular 中本地实现的方式。
  • 太棒了!谢谢!以及与其他答案的绝佳链接!绝对值得一试
【解决方案2】:

服务函数只会被调用一次,所以你的“类方法”只会用你的两种方法创建一次。但是,出于以下原因,我不会使用您的第一种方法。

  • 你不能使用继承
  • 你必须把你的整个班级放在一个大函数中
  • 您必须注意不要在构造函数中定义之前调用函数。

相反,我会做一些更像你的第二种方法的事情。例如

myApp.service('helloWorld', HelloWorldService);

function HelloWorldService() {
    this.msg = "Hello, World!";
}

HelloWorldService.prototype.showMessage = function() {
    return this.msg;
};

或者在 ES6 中你可以这样做

myApp.service('helloWorld', HelloWorldService);

class HelloWorldService {
    constructor() {
        this.msg = "Hello, World!";
    }

    showMessage() {
        return this.msg;
    }
}

编辑

如果您希望能够在每次注入时都获得类的新实例,您可以用工厂包装它。我添加了$log 来展示 DI 是如何工作的:

myApp.factory('buildHelloWorld', function($log) {
    return function() {
        return new HelloWorld($log);
    }
});

function HelloWorld($log) {
    this.msg = "Hello, World!";
    $log.info(this.msg);
}

HelloWorld.prototype.showMessage = function() {
    return this.msg;
};

然后您可以将buildHelloWorld() 函数注入控制器并调用它以获取HelloWorld 的实例。

//controller
var myCtrl = function(buildHelloWorld) {
    this.helloWorld = buildHelloWorld();
}

【讨论】:

  • 你是对的抢劫,但我想我正在寻找的是一个工厂,因为我需要创建一个共享通用方法的对象的不同实例。我没有很清楚工厂和服务之间的区别。但是您的方法确实是定义服务的好方法,使代码更清晰和可模块化。事实上,我搜索了 Angularjs 的代码,他们建议定义一个与您几乎相同的服务:
  • link 在第 4087 - 4122 行(最好用记事本打开)
  • @GabrielRequenaGarcía 恰恰相反。 service 允许您使用 new 创建单个实例,而工厂返回并反对
  • @GabrielRequenaGarcía 我添加了一个编辑,显示如何每次使用工厂获取新实例,但也许你已经想通了
  • @rob,是的,我在源代码中看到它在调用服务时如何使用new 创建一个新实例,但它只在服务的定义中创建,而不是每次你调用它作为控制器中的依赖项。这就是 tanc 回答的原始示例中发生的情况,当我修改某些属性时,它会影响所有实例。您的编辑是我正在寻找的,但我将 tanc 的答案标记为正确,因为除了解释与答案相同的近似值之外,他提供的另一个答案的链接也很棒。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-07
  • 1970-01-01
  • 2013-05-20
  • 2010-09-28
相关资源
最近更新 更多