对构造函数的JavaScript原型继承感到困惑

时间:2012-07-26 04:57:01

标签: javascript class inheritance constructor prototype

我已阅读有关JavaScript原型继承的页面和页面,但我没有找到任何涉及使用涉及验证的构造函数的地址。我已经设法使这个构造函数工作,但我知道它不理想,即它没有利用原型继承:

function Card(value) {
    if (!isNumber(value)) {
        value = Math.floor(Math.random() * 14) + 2;
    }

    this.value = value;
}

var card1 = new Card();
var card2 = new Card();
var card3 = new Card();

这导致三个Card对象具有随机值。但是,我理解的方式是每次我以这种方式创建一个新的Card对象时,它都在复制构造函数代码。我应该使用原型继承,但这不起作用:

function Card(value) {
    this.value = value;
}

Object.defineProperty( Card, "value", {
    set: function (value) {
        if (!isNumber(value)) {
            value = Math.floor(Math.random() * 14) + 2;
        }

        this.value = value;
    }
});

这也不起作用:

Card.prototype.setValue = function (value) {
    if (!isNumber(value)) {
        value = Math.floor(Math.random() * 14) + 2;
    }

    this.value = value;
};

首先,我不能再打电话给new Card()了。相反,我必须致电var card1 = new Card(); card1.setValue();这对我来说似乎非常低效和丑陋。但真正的问题是它将每个Card对象的value属性设置为相同的值。救命啊!


修改

根据Bergi的建议,我修改了代码如下:

function Card(value) {
    this.setValue(value);
}

Card.prototype.setValue = function (value) {
    if (!isNumber(value)) {
        value = Math.floor(Math.random() * 14) + 2;
    }

    this.value = value;
};

var card1 = new Card();
var card2 = new Card();
var card3 = new Card();

这导致三个Card对象具有随机值,这很好,我可以稍后调用setValue方法。当我尝试扩展课程时它似乎没有转移:

function SpecialCard(suit, value) {
    Card.call(this, value);

    this.suit = suit;
}

var specialCard1 = new SpecialCard("Club");
var specialCard2 = new SpecialCard("Diamond");
var specialCard3 = new SpecialCard("Spade");

我现在收到错误this.setValue is not a function


编辑2

这似乎有效:

function SpecialCard(suit, value) {
    Card.call(this, value);

    this.suit = suit;
}

SpecialCard.prototype = Object.create(Card.prototype);
SpecialCard.prototype.constructor = SpecialCard;

这是一个很好的方法吗?


最终编辑!

感谢Bergi和Norguard,我终于落实了这个实现:

function Card(value) {
    this.setValue = function (val) {
        if (!isNumber(val)) {
            val = Math.floor(Math.random() * 14) + 2;
        }

        this.value = val;
    };

    this.setValue(value);
}

function SpecialCard(suit, value) {
    Card.call(this, value);

    this.suit = suit;
}

Bergi帮助我确定了为什么我无法继承原型链,而Norguard解释了为什么最好不要使用原型链。我喜欢这种方法,因为代码更清晰,更容易理解。

2 个答案:

答案 0 :(得分:2)

  

我理解的方式是每次我以这种方式创建一个新的Card对象时,它都是复制构造函数代码

不,它正在执行它。没问题,你的构造函数也很完美 - 这就是它的样子。

只有在创建值时才会出现问题。每次调用函数都会创建自己的一组值,例如私有变量(你没有)。他们通常会收集垃圾,除非你创建另一个特殊值,一个特权方法,这是一个公开的函数,它包含对它所在范围的引用。是的,每个对象都有自己的“副本” “这些函数,这就是为什么你应该将不访问私有变量的所有东西都推送到原型。

 Object.defineProperty( Card, "value", ...

等等,不。在这里,您可以在构造函数上定义一个属性,即函数Card。这不是你想要的。您可以在实例上调用此代码,是的,但请注意,在评估this.value = value;时,它会以递归方式调用自身。

 Card.prototype.setValue = function(){ ... }

这看起来不错。当您稍后要使用验证代码时,您可能需要在Card个对象上使用此方法,例如在更改Card实例的值时(我不这么认为,但我不知道)知道吗?)。

  

然后我不能再拨打新卡()

哦,当然可以。该方法由所有Card实例继承,包括应用构造函数的实例(this)。你可以从那里轻松调用它,所以声明你的构造函数如下:

function Card(val) {
    this.setValue(val);
}
Card.prototype...
  

当我尝试扩展课程时似乎没有转移。

是的,它没有。调用构造函数不会设置原型链。使用new keyword实例化具有其继承的对象,然后应用构造函数。使用您的代码,SpecialCard继承自SpecialCard.prototype对象(它本身继承自默认的Object原型)。现在,我们可以将它设置为与普通卡相同的对象,或者让它从那个继承。

SpecialCard.prototype = Card.prototype;

所以现在每个实例都继承自同一个对象。这意味着,SpecialCard s将没有普通Card没有的特殊方法(来自原型)......此外,instanceof operator将无法正常工作。

所以,有一个更好的解决方案。让SpecialCard的原型对象继承自Card.prototype!这可以通过使用Object.create(并非所有浏览器都支持,您可能需要workaround)来完成,这可以完成这项工作:

SpecialCard.prototype = Object.create(Card.prototype, {
    constructor: {value:SpecialCard}
});
SpecialCard.prototype.specialMethod = ... // now possible

答案 1 :(得分:2)

就构造函数而言,每张卡都获得了构造函数内定义的任何方法的唯一副本:

this.doStuffToMyPrivateVars = function () { };

var doStuffAsAPrivateFunction = function () {};

他们获得自己唯一副本的原因是因为只有与对象本身同时实例化的唯一函数副本才能访问所附的值。

通过将它们放入原型链中,您:

  1. 将它们限制为一个副本(除非在创建后按实例手动覆盖)
  2. 删除方法访问任何私有变量的能力
  3. 通过在 EVERY 实例,中间程序中更改原型方法/属性,让朋友和家人感到非常轻松。
  4. 事情的实际情况是,除非你打算制作一款运行在旧黑莓或古老iPod Touch上的游戏,否则你不必过多担心所附功能的额外开销。

    此外,在日常的JS编程中,来自正确封装对象的额外安全性,以及模块/显示模块模式和带有闭包的沙盒的额外好处 VASTLY OUTWEIGHS 将附加到函数的方法的冗余副本的成本。

    另外,如果您确实真的那么关心,您可能会查看实体/系统模式,其中实体几乎只是数据对象(如果需要隐私,则使用自己独特的get / set方法) ......并且每个特定类型的实体都注册到为该实体/组件类型定制的系统。

    IE:你有一个卡片实体来定义套牌中的每张卡片。 每张卡都有CardValueComponentCardWorldPositionComponentCardRenderableComponentCardClickableComponent等等。

    CardWorldPositionComponent = { x : 238, y : 600 };
    

    然后将每个组件注册到系统中:

    CardWorldPositionSystem.register(this.c_worldPos);
    

    每个系统都包含通常在组件中存储的值上运行的方法的 ALL 。 系统(而不是组件)将根据需要来回聊天,以便在同一实体共享的组件之间来回发送数据(即:可以从不同的系统查询Spa的位置/值/图像的Ace,以便每个人都保持最新状态。)

    然后,不是更新每个对象 - 传统上它将是:

    Game.Update = function (timestamp) { forEach(cards, function (card) { card.update(timestamp); }); };
    Game.Draw = function (timestamp, renderer) { forEach(cards, function (card) { card.draw(renderer); }); };
    

    现在更像是:

    CardValuesUpdate();
    CardImagesUpdate();
    CardPositionsUpdate();
    RenderCardsToScreen();
    

    在传统更新内部,每个项目都处理自己的输入处理/移动/模型更新/ Spritesheet-Animation / AI等等,您将逐个更新每个子系统,并且每个子系统都是通过一个接一个地在该子系统中具有注册组件的每个实体。

    因此,独特功能的数量会减少内存占用量。 但就思考如何去做而言,这是一个非常不同的世界。