Javascript 对构造函数参数的继承依赖

Javascript inheritance dependency on constructor parameter

我想在 Angular 中实现原型继承,其中基本类型定义为 Angular 值。问题是设置我的子类型原型。假设这个简化的例子:

文件 1

angular.module("Test")
       .value("BaseController", BaseController);

BaseController.$inject = [...];

function BaseController(...) {
    ...
}

BaseController.prototype.func = function () {
    ...
};

文件 2

angular.module("Test")
       .controller("ChildController", ChildController);

ChildController.$inject = ["BaseController", ...];

function ChildController(BaseController, ...) {
    BaseController.call(this, ...);

    // save reference for later
    ChildController.base = BaseController;
    ...
}

// ERROR - Clearly why
ChildController.prototype = Object.create(BaseController.prototype);

// ERROR - instance properties aren't available on this level
ChildController.prototype = Object.create(ChildController.base.prototype);

继承

问题是在实例化构造函数之前生成了原型。但是在实例化构造函数之前,我没有可能引用 angular-injected BaseController.

我能看到解决这个问题的唯一方法是让我的 BaseController 公开定义,这样我就可以在 angular 将它注入我的构造函数之前访问它。我不喜欢这样,因为我不能在函数闭包中使用我的代码 private,而且我还想尽可能多地使用 angular 的功能,而无需通常 Javascript 与 Angular 代码的混合。

主要问题

有没有什么方法可以通过在 angular 中将基类型定义为值(或其他)来使原型继承工作?

如果您正在尝试扩展控制器来创建混合宏,那么原型继承不是您想要做的。

有关扩展控制器行为的示例,请查看此 SO 问题: angular-extending-controller

此解决方案专门针对您的方法。您可以使用模块的 run 块来分配原型。在文件 2 中添加以下内容:

angular.module("Test").run(function(BaseController) {
  ChildController.prototype = Object.create(BaseController.prototype);
});

BaseController 被注入并可用于创建原型。由于此代码在任何控制器被实例化之前运行,您将获得原型继承。

另请记住,ChildController.$inject 必须包含所有 BaseController.$inject

另一种方法是将 BaseController 附加到模块本身:

angular.module("Test").BaseController = BaseController;
...
ChildController.prototype = Object.create(angular.module("Test").BaseController.prototype);

代码仍然是私有的,构造函数仍然只能通过模块访问。

您还可以寻找继承的替代方法。根据情况,分层控制器可能是一个可行的解决方案。

<div ng-controller="BaseController"><%-- Handle Generic stuff --%>
  ...
  <div ng-controller="ChildController"><%-- Handle specific stuff --%>

Important info

  1. This solution is implemented specifically for Angular-related code that's using actual Javascript prototypes to define controllers/services and not just anonymous functions as shown in everyday examples on the web - this means following John Papa's styleguide and extending it even further as he doesn't use real prototypes

  2. All constructors must use explicit DI annotations using $inject static property. This limitation can be somewhat worked around by

    • using Angular injector's annotate (if annotations are provided inline - array) or
    • changing .inherits signature to include all base type's constructor parameters in correct order as its own parameters i.e.
      Child.inherits(Base, baseInjection1, baseInjection2, ...)

在 Angular

中设置适当的原型继承

我提出了一种相当简单且最通用的方法来设置我的 Angular 控制器的类型继承。好吧,这种继承甚至超越了这一点,可以与任何使用类型构造函数(控制器、服务等)的Angular资产一起使用

生成的解决方案将原始文件的内容更改为这种极其简单的形式:

文件 1

angular.module("Test")
       .value("BaseController", BaseController);

BaseController.$inject = [...];

function BaseController(...) {
    ...
}

BaseController.prototype.func = function () {
    ...
};

文件 2

angular.module("Test")
       .controller("ChildController", ChildController);

ChildController.$inject = ["BaseController", ...];

function ChildController(BaseController, ...) {
    // ALL MAGIC IS HERE!
    ChildController.inherits(BaseController, arguments);
    ...
}

所以我们所要做的就是在子类型的构造函数中调用一次并提供基类型(由 Angular DI 为我们注入到构造函数中)和子类型的构造函数参数(以便继承可以使用它们when 运行 基类型的构造函数)。

实现 .inherit 功能

为了让事情变得通用,我已经将这个函数添加到 Function.prototype 对象中,这样它就可以用于所有函数(或者更好的是 constructors)。这是它的实现方式:

Function.prototype.inherits = function inherits(BaseType, constructorInjections) {
    /// <summary>Sets type's inheritance to base type.</summary>
    /// <param name="BaseType" type="Function" optional="false">Base type to set for this child type.</param>
    /// <param name="constructorInjections" type="Array" optional="true">Child type constructor injection instances.</param>

    // return if both angular types don't use explicit DI
    if (!this.$inject || !BaseType.$inject) return;

    // DRY
    if (this.prototype.__proto__ === BaseType.prototype || Object.getPrototypeOf(this.prototype) === BaseType.prototype) return;

    // #region construct base object instance

    // make a quick-search dictionary of child constructor injections
    for (var i = 0, l = this.$inject.length, dic = {}; i < l; i++)
    {
        dic[this.$inject[i]] = i;
    }

    // create base type's constructor injections array
    for (var i = 0, l = BaseType.$inject.length, baseParams = []; i < l; i++)
    {
        baseParams.push(constructorInjections[dic[BaseType.$inject[i]]]);
    }

    // get base type's constructed instance
    var baseInstance = BaseType.apply(baseInstance = {}, baseParams) || baseInstance;

    // #endregion

    // #region set type inheritance chain
    if (Object.setPrototypeOf)
    {
        Object.setPrototypeOf(this.prototype, BaseType.prototype);
    }
    else
    {
        // doesn't do the same thing, but it should work just as well
        angular.extend(this.prototype, BaseType.prototype, { __proto__: BaseType.prototype });
    }
    // #endregion

    // #region add base class generated instance to prototype
    for (var i = 0, keys = Object.keys(baseInstance), l = keys.length; i < l; i++)
    {
        this.prototype[keys[i]] = baseInstance[keys[i]];
    }
    // #endregion
};