2012-07-26 101 views
2

我读过关于JavaScript原型继承的页面和页面,但是我没有发现任何使用涉及验证的构造函数的地址。我已经成功地得到这个构造工作,但我知道这是不理想的,即它不采取原型继承的优势:对构造函数的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; 
}; 

一两件事,我不能再调用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(); 

这导致使用随机值,这是伟大的三个对象,我可以打电话稍后的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; 

这是一个好办法做到这一点?


Final Edit!

感谢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解释为什么最好不要与淤泥原型链。我喜欢这种方法,因为代码更清晰,更易于理解。

+0

据我所知,原来的代码是完全没问题的。 – Inkbug 2012-07-26 05:01:47

回答

2

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

号的方式,它是执行它。没有问题,而且你的构造函数完美无缺 - 这就是它的样子。

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

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对象(该对象本身从默认对象原型继承)。现在,我们可以将它设置为与普通卡片相同的对象,或者让它继承它。

SpecialCard.prototype = Card.prototype; 

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

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

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

请参阅修订后的代码。 – 2012-07-26 05:36:41

2

在构造方面,每块卡都取得自己,

this.doStuffToMyPrivateVars = function() { }; 

var doStuffAsAPrivateFunction = function() {}; 

他们得到了自己独特的副本的原因是:的构造函数中定义的任何方法独特副本只有与对象本身同时实例化的函数的唯一副本才能访问所包含的值。

通过把它们放在原型链中,您可以:

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

问题的实质是,除非你计划制作一款运行在旧黑莓或古老的iPod Touch上的游戏,否则你不必过多担心封装函数的额外开销。

此外,在一天到一天的JS编程,从正常封装对象的额外的安全性,加上模块/揭示模块的模式获得额外的好处和沙箱与封大大超过具有成本附加到函数的方法的冗余副本。另外,如果你确实真的担心,你可能会看看实体/系统模式,其中实体几乎只是数据对象(如果需要隐私需要使用自己独特的get/set方法)... ...并且每种特定类型的实体都被注册到为该实体/组件类型定制的系统中。

IE:你需要一个卡实体来定义每张卡片。每张卡具有CardValueComponentCardWorldPositionComponentCardRenderableComponentCardClickableComponent等。

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

每个这些部件的随后登记到系统:

CardWorldPositionSystem.register(this.c_worldPos); 

每个系统保持的这通常被存储在所述部件中的值运行方法ALL。 系统(而不是组件)会根据需要在相同实体共享的组件之间来回交换数据(即:可能会从不同系统查询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-Animation/AI /等等,你需要一个接一个地更新每个子系统,每个子系统都要经过每个在该子系统中有一个注册组件的实体,另一个。

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

+0

这是使用传统对象构造函数与添加到原型对象的优点/缺点的很好解释。我一定要看看实体/系统模式! – 2012-07-26 06:02:29