2012-07-26 133 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