混淆了JavaScript原型继承与构造函数

混淆了JavaScript原型继承与构造函数,javascript,class,inheritance,constructor,prototype,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

我已经阅读了很多关于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对象时,它都在复制构造函数代码。我应该改为使用原型继承,但这不起作用:

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;
};
首先,我不能再叫新卡了。相反,我必须调用var card1=新卡;card1.setValue;对我来说,这似乎非常低效和丑陋。但真正的问题是它将每个卡片对象的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();
这会产生三个随机值的卡片对象,这很好,我可以稍后调用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现在不是函数

编辑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解释了为什么最好不要破坏原型链。我喜欢这种方法,因为代码更清晰,更容易理解

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

不,它正在执行它。没有问题,而且你的构造函数工作得很完美——这就是它应该看起来的样子

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

 Object.defineProperty( Card, "value", ...
等等,不是。这里你在构造函数上定义了一个属性,函数卡。这不是你想要的。可以在实例上调用此代码,但请注意,在计算this.value=value时;它会递归地调用自己

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

但是我不能再叫新卡了

哦,当然可以。该方法由所有卡实例继承,包括应用构造函数的卡实例。您可以很容易地从那里调用它,因此如下声明您的构造函数:

function Card(val) {
    this.setValue(val);
}
Card.prototype...
但是,当我尝试扩展类时,它似乎没有转移

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

SpecialCard.prototype = Card.prototype;
所以现在每个实例都从同一个对象继承。这意味着,特殊卡片将没有普通卡片所没有的原型中的特殊方法。。。此外,该选项将不再正常工作

因此,有一个更好的解决方案。让SpecialCards原型对象从Card.prototype继承!这可以通过使用并非所有浏览器都支持的浏览器来实现,您可能需要一个专门用于完成此任务的:

SpecialCard.prototype = Object.create(Card.prototype, {
    constructor: {value:SpecialCard}
});
SpecialCard.prototype.specialMethod = ... // now possible
我的理解是,每次我以这种方式创建一个新的Card对象时,它都在复制构造函数代码

不,它正在执行它。没有问题,而且你的构造函数工作得很完美——这就是它应该看起来的样子

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

 Object.defineProperty( Card, "value", ...
等等,不是。这里你在构造函数上定义了一个属性,函数卡。这不是你想要的。可以在实例上调用此代码,但请注意,在计算this.value=value时;它会递归地调用自己

 Card.prototype.setValue = function(){ ... }
这看起来不错。你可能需要这个 方法在以后使用验证代码时,例如在更改卡实例的值时,我不这么认为,但我不知道

但是我不能再叫新卡了

哦,当然可以。该方法由所有卡实例继承,包括应用构造函数的卡实例。您可以很容易地从那里调用它,因此如下声明您的构造函数:

function Card(val) {
    this.setValue(val);
}
Card.prototype...
但是,当我尝试扩展类时,它似乎没有转移

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

SpecialCard.prototype = Card.prototype;
所以现在每个实例都从同一个对象继承。这意味着,特殊卡片将没有普通卡片所没有的原型中的特殊方法。。。此外,该选项将不再正常工作

因此,有一个更好的解决方案。让SpecialCards原型对象从Card.prototype继承!这可以通过使用并非所有浏览器都支持的浏览器来实现,您可能需要一个专门用于完成此任务的:

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

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

this.doStuffToMyPrivateVars = function () { };

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

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

将它们限制为一个副本,除非在创建后手动覆盖每个实例 删除该方法访问任何私有变量的能力 通过在每个实例(mid程序)上更改原型方法/属性,使其真正容易挫败朋友和家人。 事实是,除非你打算制作一款运行在旧黑莓或旧iPodtouch上的游戏,否则你不必太担心附带功能的额外开销

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

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

IE:你会有一个卡片实体来定义卡片组中的每张卡片。 每张卡都有一个CardValueComponent、一个CardWorldPositionComponent、一个CardRenderableComponent、一个CardClickableComponent等等

CardWorldPositionComponent = { x : 238, y : 600 };
然后将这些组件中的每一个注册到系统:

CardWorldPositionSystem.register(this.c_worldPos);
每个系统都保存所有通常会在组件中存储的值上运行的方法。 系统而非组件将根据需要在同一实体共享的组件之间来回聊天,以发送数据,即:Spade的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动画/AI/等等,您将一个接一个地更新每个子系统,每个子系统将逐个通过在该子系统中具有注册组件的每个实体

因此,在独特函数的数量上,内存占用更小。
但就如何实现这一点而言,这是一个完全不同的世界。

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

this.doStuffToMyPrivateVars = function () { };

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

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

将它们限制为一个副本,除非在创建后手动覆盖每个实例 删除该方法访问任何私有变量的能力 通过在每个实例(mid程序)上更改原型方法/属性,使其真正容易挫败朋友和家人。 事实是,除非你打算制作一款运行在旧黑莓或旧iPodtouch上的游戏,否则你不必太担心附带功能的额外开销

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

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

IE:你会有一个卡片实体来定义卡片组中的每张卡片。 每张卡都有一个CardValueComponent、一个CardWorldPositionComponent、一个CardRenderableComponent、一个CardClickableComponent等等

CardWorldPositionComponent = { x : 238, y : 600 };
然后将这些组件中的每一个注册到系统:

CardWorldPositionSystem.register(this.c_worldPos);
每个系统都保存所有通常会在组件中存储的值上运行的方法。 系统而非组件将根据需要在同一实体共享的组件之间来回聊天,以发送数据,即:Spade的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动画/AI/等等,您将一个接一个地更新每个子系统,每个子系统将逐个通过在该子系统中具有注册组件的每个实体

因此,在独特函数的数量上,内存占用更小。
但就如何实现这一点而言,这是一个完全不同的世界。

据我所知,原始代码是完全正确的。据我所知,原始代码是完全正确的。这很好地解释了使用传统对象构造函数与添加到原型对象相比的优缺点。我一定要研究实体/系统模式!这很好地解释了使用传统对象构造函数与添加到原型对象的优缺点。我一定要研究实体/系统模式!