困惑与构造JavaScript的原型继承(Confused about JavaScript pro

2019-07-30 14:51发布

I've read pages and pages about JavaScript prototypal inheritance, but I haven't found anything that addresses using constructors that involve validation. I've managed to get this constructor to work but I know it's not ideal, i.e. it's not taking advantage of prototypal inheritance:

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();

This results in three Card objects with random values. However, the way I understand it is that each time I create a new Card object this way, it is copying the constructor code. I should instead use prototypal inheritance, but this doesn't work:

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;
    }
});

This doesn't work either:

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

    this.value = value;
};

For one thing, I can no longer call new Card(). Instead, I have to call var card1 = new Card(); card1.setValue(); This seems very inefficient and ugly to me. But the real problem is it sets the value property of each Card object to the same value. Help!


Edit

Per Bergi's suggestion, I've modified the code as follows:

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();

This results in three Card objects with random values, which is great, and I can call the setValue method later on. It doesn't seem to transfer when I try to extend the class though:

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");

I get the error this.setValue is not a function now.


Edit 2

This seems to work:

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

    this.suit = suit;
}

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

Is this a good way to do it?


Final Edit!

Thanks to Bergi and Norguard, I finally landed on this implementation:

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 helped me identify why I wasn't able to inherit the prototype chain, and Norguard explained why it's better not to muck with the prototype chain at all. I like this approach because the code is cleaner and easier to understand.

Answer 1:

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

不,它是执行它。 没有问题,你的构造完美的作品 - 这是应该的样子。

当你创造价值,只会出现问题。 函数的每次调用创建其自己的一套价值观,如私有变量(你没有任何)。 他们通常会得到垃圾回收,除非再创建一个特殊的价值, 优越的方法 ,它是保存到它生活在范围内的参考暴露的功能。是的,每个对象都有这样的功能了自己的“拷贝”,这是你为什么要推不访问私有变量,以原型的一切。

 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关键词 ,其继承的对象实例化,然后构造应用。 与您的代码, SpecialCard从S继承SpecialCard.prototype对象(其本身从默认的对象原型继承)。 现在,我们可以要么只是将其设置为相同的对象为普通卡,或者让它从一个继承。

SpecialCard.prototype = Card.prototype;

所以,现在每个实例从同一个对象继承。 这意味着, SpecialCard旨意没有什么特别的方法(从原型),正常的Card都不具备的......此外, instanceof运算符将无法正常工作了。

所以,有一个更好的解决方案。 让SpecialCard从s原型对象继承Card.prototype ! 这可以通过使用来实现Object.create (不是所有的浏览器都支持,你可能需要一个解决办法 ),这是专门做的正是这种工作:

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


Answer 2:

在构造方面,每张卡是获得的构造函数中定义的任何方法自己的独特的副本:

this.doStuffToMyPrivateVars = function () { };

要么

var doStuffAsAPrivateFunction = function () {};

他们得到了自己独特的副本的原因是因为功能只有独特的副本,同时为对象本身实例化时,将不得不进入封闭值。

通过把他们的原型链中,您可以:

  1. 他们限制在一个副本(每个实例,除非手动覆盖,创建后)
  2. 删除方法的访问任何私有变量能力
  3. 使它很容易通过改变原型方法/属性上的每一个实例,中期计划挫败的朋友和家人。

事情的现实是,除非你在做的是旧黑莓或一个古老的iPod Touch上运行的游戏策划,你不用担心的封闭功能的额外开销太大。

此外,在每天的日常JS编程,从正常封装对象的额外的安全性,加上模块/揭示模块的模式获得额外的好处,并封锁沙箱大大超过了具有附着功能的方法冗余拷贝的成本。

此外,如果你真的,真的那么关心,你可以做一下实体/系统模式,其中实体是非常简单,只是数据对象(用自己独特的get / set方法,如果需要隐私)... ......每一个特定类型的那些实体的注册到这是自该实体/组件制成型的系统。

IE:你就会有一个卡实体在甲板来定义每个卡。 每张卡都有一个CardValueComponent ,一个CardWorldPositionComponent ,一个CardRenderableComponent ,一个CardClickableComponent ,等等。

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

那么这些组件中的每一个注册到系统:

CardWorldPositionSystem.register(this.c_worldPos);

每个系统保持所有这些通常被存储在所述部件中的值运行的方法。 描述的系统(而不是组件)将聊天来回,根据需要发送数据来回,由相同的实体共享的组件之间(即:铲形的位置/值/图像的埃斯可能来自不同系统的查询,使得每个人都保持最新)。

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

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 /等等,你要更新各个子系统此起彼伏,每个子系统正在经历各实体具有注册的组件在该子系统中,一个接一个。

所以这是对的独特功能的数量较小的内存空间占用。 但它在思考如何做到这一点的方面一个非常不同的世界。



文章来源: Confused about JavaScript prototypal inheritance with constructors