使用 Angularjs module.service 的原型继承

Prototypal inheritance using Angularjs module.service

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

我在一个项目中工作,我需要实例化一个 "terminal" class,它具有构造函数属性和一系列方法(通常 class)。在普通 JS 中,我会使用伪classical 模式(将构造函数属性与原型链中的方法分开)。

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

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

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

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

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

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

另外,如果可以的话(如果真的能提高代码的效率),请问有什么实现方式? (上面的代码只是一个大胆的猜测)

谢谢

服务功能只会被调用一次,因此您的 "class methods" 只会用您的两种方法创建一次。但是,出于以下原因,我不会使用您的第一种方法。

  • 你不能使用继承
  • 你必须将整个 class 放在 1 个大函数中
  • 你必须小心,不要在构造函数中定义函数之前调用它。

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

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

编辑

如果你想在每次注入时都能得到你的 class 的新实例,你可以用工厂包装它。我添加了 $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();
}

根据评论进行编辑:看起来如果您只是 return 对象的构造函数就可以做到这一点,然后通过调用服务访问原型。

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

参见下面的示例:

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 中可以找到对此的另一个很好的解释。这可能显示了一种 better/cleaner 方法来执行此示例中使用提供程序显示的内容(服务和工厂均派生自该服务和工厂,请参阅作者的旁注)。

下方原始 post 的其余部分作为背景

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>

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

看起来你真正想要的是一个工厂(虽然这主要是语义,你可以在下一个例子中将 "factory" 换成 "service",它会产生相同的结果。这可以也可以在 Angular 文档中看到。documentation for a service 从未实际使用 .service,它始终使用 .factory)。通过这种方式,在本例中调用 'myService(...)' 时,您实质上可以构造工厂对象的新实例。使用这些函数参数,您可以自定义 return 对象的属性,包括函数,如您在示例中所见。

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>