如何在JavaScript中扩展闭包后面定义的类?

Cha*_*ian 10 javascript

我有一组JavaScript"类",其中基类定义函数,然后由继承的类共享.它工作正常,它设置如下:

var ThingA = function(name) {
    this.name = name;
};

ThingA.prototype = {
    sayHi: function() {
        alert('Hi, ' + this.name + '!');
    }
};


var ThingB = function() {
    ThingA.call(this, 'Charlie');
};

ThingB.prototype = new ThingA();
ThingB.prototype.constructor = ThingB;

var instanceOfB = new ThingB();
instanceOfB.sayHi();   // alerts 'Hi, Charlie!'
Run Code Online (Sandbox Code Playgroud)

由于我无法控制的原因,我的公司在编写JavaScript时更喜欢遵循这种模式:

SomeClass = function() {

    // "Private" functions go here

    function somePrivateMethod() { 
        ...
    }

    return {

        // "Public" methods go here
        somePublicMethod: function() { ... }

    };
}();
Run Code Online (Sandbox Code Playgroud)

现在,就事情而言,这很好,并且在许多情况下它都能很好地运行.但它更像是一种功能性风格.只有一个"类"实例,一切都是静态的.

我被要求修改我的工作代码,以更贴近我公司喜欢的风格.所以我的问题是,有没有办法从包含在工厂类中的类继承?它看起来像这样:

FactoryClassA = function() {

    var ThingA = function(name) {
        this.name = name;
    };

    ThingA.prototype = {
        sayHi: function() {
            alert('Hi, ' + this.name + '!');
        }
    };

    return {
         createThingA: function(name) {
             return new ThingA(name);
         }
    };
}();


FactoryClassB = function() {

    // Define a ThingB class that inherits from ThingA somehow

    return {
         createThingB: function() {
             return new ThingB();
         }
    };
}();


var instanceOfB = FactoryClassB.createThingB();
instanceOfB.sayHi();   // should alert 'Hi, Charlie!'
Run Code Online (Sandbox Code Playgroud)

有没有定义的方式ThingB包裹在FactoryClassB继承从ThingA包裹FactoryClassA 感谢这个问题,我知道我不能完全像这样做.我正在考虑使用一种方法来扩展给定的类......不知何故?

这个答案似乎很接近,但我无法弄清楚如何修改该示例的细节以适应我的具体情况.我愿意稍微改变我公司的惯常模式,但我能否至少接近它?

更新1

为了回应Adam的评论,只是在工厂类中添加一个参数,这就是我被困的地方:

ThingB.prototype = new ThingA();
ThingB.prototype.constructor = ThingB;
Run Code Online (Sandbox Code Playgroud)

如果我只是将参数传递给工厂类方法,我无法弄清楚如何调整这些线以使其工作.

kri*_*isk 4

以下是(我相信)您正在寻找的内容:

FactoryClassA = function() {
    var ThingA = function(name) {
       this.name = name;
    };
    ThingA.prototype = {
        sayHi: function() {
            console.log('Hi, ' + this.name + '!');
        }
    };
    // Add the constructor back to the prototype
    // (see explanation below)
    ThingA.prototype.constructor = ThingA;

    return {
        createThingA: function(name) {
            return new ThingA(name);
        }
    };
}();

FactoryClassB = function() {
    // Bootstrapping:
    // Capture the instance, as we'll need it to set up the prototype
    var baseInstance = new FactoryClassA.createThingA();
    // Capture the constructor
    var baseConstructor = baseInstance.constructor;
    // Keep a reference to the base prototype
    var baseProto = baseConstructor.prototype;

    function ThingB(name) {
        // Call base constructor, along with our args
        baseConstructor.call(this, name);
    };
    ThingB.prototype = baseInstance;
    ThingB.prototype.constructor = ThingB;

    ThingB.prototype.sayHi = function() {
        console.log('here I am');
        // call the base class `sayHi`
        baseProto.sayHi.call(this);
    };

    return {
        createThingB: function(name) {
            return new ThingB(name);
        }
    };
}();

// Testing
var foo = FactoryClassB.createThingB("Indeed");
foo.sayHi();

// Output:
//   here I am 
//   hi indeed
Run Code Online (Sandbox Code Playgroud)

解释

在 中FactoryClassA,这一行是必要的:

ThingA.prototype.constructor = ThingA;
Run Code Online (Sandbox Code Playgroud)

请注意,JS 中的每个原型都是通过对其构造函数的引用自动创建的。例如,当您执行以下操作时:

function T(){}
Run Code Online (Sandbox Code Playgroud)

T.prototype已经有一个名为 的属性 constructor ,它指向T.

然而,在您的实现中ThingA,您通过执行 重置了整个原型ThingA.prototype = { ... }。因此,您现在已经失去了对其构造函数的引用。在 99% 的情况下这是可以的,并且不会产生任何负面影响(这可能就是大多数开发人员倾向于忘记它的原因)。然而,在继承的情况下,这可能是必要的。

现在,在 中FactoryClassB,我们需要进行一些引导:

var baseInstance = new FactoryClassA.createThingA();
var baseConstructor = baseInstance.constructor;
var baseProto = baseConstructor.prototype;
Run Code Online (Sandbox Code Playgroud)

观察最后两行,因为它们对于在此设计模式中实现继承至关重要。首先,由于ThingA的构造函数可以通过原型 ( ThingA.prototype.constructor = ThingA) 访问,那么这意味着给定 的实例ThingA,我们可以直接检索其构造函数。由于构造函数就是函数本身,并且每个函数都有对其原型的引用,因此我们可以保留ThingA.prototypewith的引用baseConstructor.prototype

接下来是关键部分,我们设置继承链:

function ThingB(name) {
    // Call the base constructor
    baseConstructor.call(this, name);
};
ThingB.prototype = baseInstance;
ThingB.prototype.constructor = ThingB;
Run Code Online (Sandbox Code Playgroud)

上面的最后一行非常重要,因为它告诉原型它的构造函数是什么,否则它仍然会指向ThingA.

这就是原型继承。

边注

您可能会看到上面的内容是如何变得非常乏味、有点怪诞和重复的。因此,您可能需要考虑像Fiber.js这样的继承库,它遵循您想要的封装模式(以及一些好处,如 mixins 和装饰器)。 免责声明:我创作了该库