2

我已经阅读了有关 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 解释了为什么最好完全不要使用原型链。我喜欢这种方法,因为代码更干净,更容易理解。

4

2 回答 2

2

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

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

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

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

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

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

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

但后来我不能再调用 new Card()

哦,当然可以。该方法由所有Card实例继承,包括应用构造函数的实例(this)。你可以很容易地从那里调用它,所以像这样声明你的构造函数:

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

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

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

SpecialCard.prototype = Card.prototype;

所以现在每个实例都继承自同一个对象。这意味着,SpecialCards 将没有普通Cards 所没有的特殊方法(来自原型)......此外,instanceof运算符将不再正常工作。

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

SpecialCard.prototype = Object.create(Card.prototype, {
    constructor: {value:SpecialCard}
});
SpecialCard.prototype.specialMethod = ... // now possible
于 2012-07-26T05:09:09.083 回答
2

就构造函数而言,每张卡片都有自己的、构造函数内部定义的任何方法的唯一副本:

this.doStuffToMyPrivateVars = function () { };

或者

var doStuffAsAPrivateFunction = function () {};

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

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

  1. 将它们限制为一个副本(除非在创建后手动覆盖每个实例)
  2. 删除该方法访问任何私有变量的能力
  3. 通过更改每个实例、程序中间的原型方法/属性,让让朋友和家人感到沮丧变得非常容易。

实际情况是,除非您打算制作在旧版 Blackberries 或旧版 iPod Touch 上运行的游戏,否则您不必太担心封闭功能的额外开销。

此外,在日常的 JS 编程中,正确封装的对象带来的额外安全性,加上模块/显示模块模式和带闭包的沙箱的额外好处,远远超过了将方法的冗余副本附加到函数的成本。

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

IE:您将有一个 Card-Entity 来定义一副牌中的每张牌。每张卡片都有 a CardValueComponent、 a CardWorldPositionComponent、 a CardRenderableComponent、 aCardClickableComponent等。

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

然后将这些组件中的每一个注册到系统中:

CardWorldPositionSystem.register(this.c_worldPos);

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

然后不是更新每个对象——传统上它会是这样的:

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-动画/AI/等等,您正在一个接一个地更新每个子系统,每个子系统都在经历每个在该子系统中具有注册组件的实体,一个接一个。

因此,独特功能的数量会占用更少的内存。但在思考如何做到这一点方面,这是一个非常不同的宇宙。

于 2012-07-26T05:53:24.163 回答