物件的繼承

物件的繼承:

繼承的目的在於重用程式碼,以及結構化的資料。在程式中,物件通用的屬性與方法,都應該用繼承的方式來進行,這是一種比較有效率的做法。

Javascript的物件繼承原理其實並不難,只是相當異於一般(基於類別)的物件導向程式。Javascript是一個基於原型的物件導向程式,他的繼承主要就是依據原型來實作。如果你學過其他的物件導向語言,可能會有點不習慣,不過沒關係,先將其他的物件導向語言的概念放一旁。重新學學Javascript的繼承方式。

Javascript的繼承作法很簡單,就是將要被繼承的"物件"指定給prototype屬性就可以了。不過這屬性不是物件本身的屬性,而是建構函式的屬性。將物件指定給建構函式的屬性,然後由這建構函式所建立的物件,都會繼承該物件的屬性。通常我們可以把要被繼承的屬性寫在一個物件實字,然後指定給建構函式的prototype。

<script type="text/javascript">
function creature(name){ //建構函式
  this.name=name.toString();
}
creature.prototype={ //將物件實字指定給建構函式的prototype屬性。
  setname:function(name){
    this.name=name.toString();},
  getname:function(){
    return this.name;},
}
var mycreature=new creature("維克");
alert("姓名: "+mycreature.getname());//mycreature繼承了物件實字中的getname()方法。
</script>

上例的程式碼中凡是藉由creature建構函式所建立的物件,都會繼承setname()與getname()兩個方法。這兩個方法當然也可以寫在建構函式內,不過這會導致每次建立物件時都要實作一次方法。不是一種有效率的做法。要記住,通用的屬性與方法,應該要寫在原型當中。那把name屬性也寫進原型中呢?將name屬性寫在原型中得小心一些狀況,就是,他其實可能會被不小心覆蓋掉,你用不到他,他卻占著記憶體。下面例子我們試著把name寫在prototype中看看。

<script type="text/javascript">
function creature(name){ //建構函式
}
creature.prototype={ //將物件實字指定給建構函式的prototype屬性。
 name:"豬頭",
 setname:function(name){
  this.name=name.toString();},
 getname:function(){
  return this.name;},
}
var mycreature=new creature("維克");
alert("姓名: "+mycreature.getname());//輸出 豬頭
mycreature.setname("維克");
alert("姓名: "+mycreature.getname());//輸出 維克
alert("姓名: "+creature.prototype.getname());//輸出 豬頭
</script>

上例原型中有一個name屬性,因此在物件mycreature新建立時,getname()方法可以讀取到name屬性,此時值為"豬頭"。但是當呼叫setname("維克")方法時,由於函式內的this會指向mycreature物件,而不是原型物件,所以會在mycreature物件中建立一個新的屬性name,其值會等於"維克",而原型物件中的name屬性並未被更改,所以再次呼叫getname()方法時會傳回mycreature物件的name屬性。換一種狀況,當你企圖在建構函式中初始化name時,其實就已經是建立了另一個name了。

function creature(name){ //建構函式
 this.name=name.toString(); 
//這會為物件建立一個name屬性,因而覆蓋掉原型中的name屬性,注意不能在這裡呼叫setname()。
}

我們用圖形解釋一下這狀況:

在存取物件屬性時,會先從最物件本身開始,再往其原型逐漸查詢過去,原型找不到就繼續找原型的原型,一直持續下去直到沒有原型為止,這種行為稱之為原型鍊。

圖中的 __proto__ 屬性會指向該物件的原型。不過這不是個標準屬性 (至少目前ECMA5還未定義該參數。),並不是每個瀏覽器都有這屬性,不過這並不妨礙我們用它來解釋繼承關係。

原型的私有屬性:

上面原型中的name屬性是公開屬性,我們來看看私有屬性會有什麼特性,我們利用閉包來產生私有屬性:

<script type="text/javascript">
function creature(name){ //建構函式
 this.name=name.toString();
 this.creature_type="精靈";
}
creature.prototype=(function(){//立即函數傳回物件 包裹私有變數 產生閉包
 var creature_type="人類"; //要記得使用var 不然會變成全域變數
 return { //傳回物件給creature.prototype
  settype:function(type){ 
  creature_type=type.toString();},//注意這裡並沒有使用this
  gettype:function(){
   return creature_type;},//注意這裡並沒有使用this
};})();
var mycreature=new creature("維克");
alert("種族: "+mycreature.gettype());//輸出 人類
mycreature.settype("獸人");
alert("種族: "+mycreature.gettype());//輸出 獸人
alert("種族: "+creature.prototype.gettype());//輸出 獸人
</script>

creature_type是原型中的一個私有屬性,當呼叫settype()時並沒有使用this,因此不會在物件本身建立另一個creature_type屬性。而且既便是在建構函式中直接為物件建立一個creature_type屬性,也不會影響到gettype()方法傳回的值,因為gettype中傳回的是原型的私有屬性。

共享的屬性與方法:

在原型中的屬性與方法,不論是公開或是私有,都是在物件中共享的,在上例子,凡是藉由creature()建構函式建構出來的物件,都會共用原型中的屬性與方法。也就是說一個物件使用settype()方法改變原型中creature_type的值,另一個物件在可以在使用gettype()時讀出改變後的值。這道理並不難,畢竟它們的原型其實就是同一個物件。

其他繼承模式:

上面解釋了prototype繼承的基本概念,或許這上面提到的特性並無法滿足你的需求,不過沒關係,Javascript的自由度造就了許多不同的繼承模型。或許有其他模型能滿足你的需求。我們後續再提。

 
 

  按個讚!~支持本站!~

FB推薦載入中