【问题标题】:Javascript inheritance and encapsulation, done efficientlyJavascript继承与封装,高效完成
【发布时间】:2015-02-17 09:23:19
【问题描述】:

来自 C++/Objective-C 背景,我正在尝试学习如何正确有效地重现 Javascript 中的继承和封装模式。我已经阅读了大量资料(Crockford 等),虽然有很多关于如何实现其中一个或另一个的示例,但我正在努力解决如何在不引入重大负面影响的情况下将它们结合起来。

目前,我有这个代码:

var BaseClass = (function() {

    function doThing() {
        console.log("[%s] Base-class's 'doThing'", this.name);
    }

    function reportThing() {
        console.log("[%s] Base-class's 'reportThing'", this.name);
    }

    return function(name) {
        var self = Object.create({});

        self.name = name;

        self.doThing = doThing;
        self.reportThing = reportThing;

        return self;
    }

}());

var SubClass = (function(base) {

    function extraThing() {
        console.log("[%s] Sub-class's 'extraThing'", this.name);
    }

    function doThing() {
        console.log("[%s] Sub-class's replacement 'doThing'", this.name);
    }    

    return function(name) {
        // Create an instance of the base object, passing our 'name' to it.
        var self = Object.create(base(name));    


        // We need to bind the new method to replace the old
        self.doThing = doThing;
        self.extraThing = extraThing;

        return self;
    }

}(BaseClass));

大部分做我想做的事:

// Create an instance of the base class and call it's two methods
var base = BaseClass("Bert");

base.doThing();         // "[Bert] Base-class's 'doThing'"
base.reportThing();     // "[Bert] Base-class's 'reportThing'"

var other = BaseClass("Fred");


// Create an instance of the sub-class and call it's three methods (two from the base, one of it's own)
var sub = SubClass("Alfred");

sub.doThing();          // "[Alfred] Sub-class's replacement 'doThing'"
sub.extraThing();       // "[Alfred] Sub-class's 'extraThing'"
sub.reportThing();      // "[Alfred] Base-class's 'reportThing'"

但是,有(至少!)两个问题:

  • 我不相信原型链是完整的。如果我通过子类的一个实例替换原型中的方法,其他实例看不到它:
  • .name 属性没有封装

我正在替换原型的函数实现,如下所示:

Object.getPrototypeOf(oneInstance).reportThing = function() { ... }
otherInstance.reportThing()    // Original version is still called

这也许不是什么大问题,但它让我怀疑自己的理解。

私有变量是我想要有效实现的东西。变量隐藏的模块模式在这里没有帮助,因为它导致函数定义存在于每个对象中。我可能错过了一种组合模式的方法,那么有没有一种方法可以在不复制函数的情况下实现私有变量?

【问题讨论】:

  • “我正在努力学习如何正确有效地重现 Javascript 中的继承和封装模式”——不要假设你从 C++ 中知道的模式在 JavaScript 中也能有效工作。
  • @joews 我不认为会有 1:1 的对应关系,但由于绝对可以单独合并这两种模式,我希望有一种干净的方式来组合它们。
  • 对我来说,这是 JavaScript 最烦人的方面之一,以至于很多人都想在上面强制执行他们的 Java / C++ 习惯。
  • 我并不是想保持这样的习惯。我很高兴能够以最佳方式构建代码,但我没有看到我想要模仿的任何概念的缺点。

标签: javascript inheritance encapsulation


【解决方案1】:

这通常是我在 JavaScript 中处理继承和封装的方式。 defclass 函数用于创建不继承自任何其他类的新类,extend 函数用于创建扩展另一个类的新类:

var base = new BaseClass("Bert");

base.doThing();     // "Bert BaseClass doThing"
base.reportThing(); // "Bert BaseClass reportThing"

var sub = new SubClass("Alfred");

sub.doThing();     // "Alfred SubClass replacement doThing"
sub.extraThing();  // "Alfred SubClass extraThing"
sub.reportThing(); // "Alfred BaseClass reportThing"

var other = new SubClass("Fred");

SubClass.prototype.reportThing = function () {
    console.log(this.name + " SubClass replacement reportThing");
};

other.reportThing(); // Fred SubClass replacement reportThing
<script>
function defclass(prototype) {
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
}

function extend(constructor, keys) {
    var prototype = Object.create(constructor.prototype);
    for (var key in keys) prototype[key] = keys[key];
    return defclass(prototype);
}

var BaseClass = defclass({
    constructor: function (name) {
        this.name = name;
    },
    doThing: function () {
        console.log(this.name + " BaseClass doThing");
    },
    reportThing: function () {
        console.log(this.name + " BaseClass reportThing");
    }
});

var SubClass = extend(BaseClass, {
    constructor: function (name) {
        BaseClass.call(this, name);
    },
    doThing: function () {
        console.log(this.name + " SubClass replacement doThing");
    },
    extraThing: function () {
        console.log(this.name + " SubClass extraThing");
    }
});
</script>

阅读以下答案以了解继承在 JavaScript 中的工作原理:

What are the downsides of defining functions on prototype this way?

它解释了原型和构造函数之间的区别。此外,它还展示了原型和类如何同构以及如何在 JavaScript 中创建“类”。

希望对您有所帮助。

【讨论】:

  • 包含很好的类定义 - 我喜欢它。让人想起 Mootools 方法。
  • 确实如此。此外,defclassextend 函数各只有三行代码。
  • 一个优雅的解决方案,但这是否解决了数据隐藏问题?我仍然可以调用 other.name 来返回“Fred”,因此我不需要访问器方法。我还没有找到解决这个问题的方法。也许 ECMA6 解决了这个问题,但 ECMA5 及以下版本不提供数据隐藏。这意味着在基类上定义的任何变量都可以从任何派生对象访问... eeek!
【解决方案2】:

简单的配方如下:

function BaseClass(someParams)
{
   // Setup the public properties, e.g.
   this.name = someParams.name;
}

BaseClass.prototype.someMethod = function(){
   // Do something with the public properties
}

现在继承以这种方式发生

function SubClass(someParams)
{ 
    // Reuse the base class constructor
    BaseClass.call(this, someParams);

    // Keep initializing stuff that wasn't initialized by the base class
    this.anotherProperty= someParams.anotherProperty;
}

// Copy the prototype from the BaseClass
SubClass.prototype = Object.create(BaseClass.prototype);
SubClass.prototype.constructor = SubClass;

// Start extending or overriding stuff
SubClass.prototype.someMethod = function(){

   // In case you still wanna have the side effects of the original method
   // This is opt-in code so it depends on your scenario.
   BaseClass.prototype.someMethod.apply(this, arguments);

   // Override the method here       
}

取自: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript

附:并非所有旧浏览器都支持 Object.create,但不用担心,此链接中有一个 polyfill。 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create

【讨论】:

  • 在 Java 中,当你重写一个函数时,你通常也会调用超级函数。你可以用 Javascript 做同样的事情:gist.github.com/StephanBijzitter/b2495ebf9702b3866c91
  • 这是可选的,所以,没有什么可以阻止您在 BaseClass.prototype.someMethod 上使用“调用”或“应用”。我将更新代码以反映这一点。谢谢...
  • 展示如何在超类上调用方法会很有用,谢谢。
【解决方案3】:

如果要保留原型链,则必须覆盖并使用 .prototype: 例子: 主类:

function BaseClass(){

}
BaseClass.prototype.doThing = function(){...}

子类:

function SubClass(){
}
SubClass.prototype= new BaseClass();
SubClass.prototype.extraThing = function(){};

现在,每当您更改 extraThing 或 doThing 时,它都会被到处替换。 name 属性可作为公共变量访问(它不是静态的)。

如果你想要它是静态的,你必须把它放在原型中。

如果你想要它私有,你可以让它在本地运行:

function BaseClass(nameParam){
 var name = nameParam;
}

要创建一个对象,只需调用函数:

var testObj = new BaseClass("test");
testObj.doThing();

如果您想将私有变量与可重写函数结合使用,您可能会找到您的answer here。但如果你能够重写可以访问私有变量的函数,它就不再是真正的私有变量了。

【讨论】:

  • 我的印象是,这些天“新”不被认为是正确的方法,并期望 Object.create(base) 负责原型链。我的理解显然需要工作。
  • 问题不是你不应该使用new,问题是如果你忘记了创建新实例的“new”
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-10-19
  • 2010-11-20
  • 1970-01-01
  • 2012-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多