1. 程式人生 > >javascript 原型繼承(第四篇)---幾種繼承方式

javascript 原型繼承(第四篇)---幾種繼承方式

今天要介紹的是,物件之間的”繼承”的幾種方式。

首先有一個問題,為什麼繼承還有幾種方式呢?你看不管是java還是C++,繼承就是繼承,哪有幾種繼承方式,不過Javascript 是一種靈活的語言,之所以靈活,說不好聽點就是設計得太簡單,連基本的繼承都要自己去實現,下面就說一下最常用的幾種繼承方式。

比如,現在有一個”動物”物件的建構函式。

function Animal(){
    this.species = "動物";
  }

還有一個”貓”物件的建構函式。

function Cat(name,color){
    this.name = name;
    this
.color = color;   }

怎樣才能使”貓”繼承”動物”呢?

一、建構函式繼承

第一種方法也是最簡單的方法,使用call或apply方法,將父物件的建構函式繫結在子物件上,即在子物件建構函式中加一行:

function Cat(name,color){
    Animal.apply(this, arguments);//在子物件增加這一行程式碼
    this.name = name;
    this.color = color;
  }
  var cat1 = new Cat("大毛","黃色");
  alert(cat1.species); // 動物
二、原型鏈繼承

第二種方法更常見,使用prototype屬性,也就是常說的原型鏈繼承。

如果”貓”的prototype物件,指向一個Animal的例項,那麼所有”貓”的例項,就能繼承Animal了。

   Cat.prototype = new Animal();
  Cat.prototype.constructor = Cat;
  var cat1 = new Cat("大毛","黃色");
  alert(cat1.species); // 動物

程式碼的第一行,我們將Cat的prototype物件指向一個Animal的例項。

Cat.prototype = new Animal();

它相當於完全刪除了prototype 物件原先指向的值,然後指向了一個新值。但是,第二行又是什麼意思呢?

Cat.prototype.constructor = Cat;//這一行什麼意思呢?

原來,任何一個建構函式都有一個prototype屬性,prototype的屬性值指向一個物件,(prototype相當於一個指標,我們把它指向的物件暫叫prototype物件),而且這個prototype物件預設有一個constructor屬性,指向這個建構函式本身。如下圖所示,

SuperType是一個建構函式,右側的方框就是它的原型,SuperType的prototype屬性指向SuperType Prototype物件,SuperType Prototype物件的constructor屬性指向它的建構函式。

如果沒有”Cat.prototype = new Animal();”這一行,Cat.prototype.constructor是指向Cat的;加了這一行以後,Cat.prototype.constructor指向Animal。

   Cat.prototype = new Animal();
   alert(Cat.prototype.constructor == Animal); //true

更重要的是,每一個例項也有一個constructor屬性,預設呼叫prototype物件的constructor屬性。

alert(cat1.constructor == Cat.prototype.constructor); // true

因此,在執行”Cat.prototype = new Animal();”這一行之後,cat1.constructor也指向了Animal。

alert(cat1.constructor == Animal); // true

這顯然會導致繼承鏈的紊亂(cat1明明是用建構函式Cat生成的,但是cat1.constructor卻指向了Animal), 因此我們必須手動糾正,將Cat.prototype物件的constructor值改為Cat。 這就是第二行的意思。

這是很重要的一點,程式設計時務必要遵守, 在用原型鏈繼承時,都要記著,如果替換了prototype物件

A.prototype = B;

那麼,下一步必然是為prototype物件的constructor屬性指回原來的建構函式。

A.prototype.constructor = A;
三、原型鏈繼承的改進

第三種方法是對第二種方法的改進。由於Animal物件中,不變的屬性都可以直接寫入Animal.prototype。所以,我們也可以讓Cat()跳過 Animal(),直接繼承Animal.prototype。

現在,我們先將Animal物件改寫:

function Animal(){ }
  Animal.prototype.species = "動物";

然後,將Cat的prototype物件,然後指向Animal的prototype物件,這樣就完成了繼承。

   Cat.prototype = Animal.prototype;
  Cat.prototype.constructor = Cat;
  var cat1 = new Cat("大毛","黃色");
  alert(cat1.species); // 動物

與前一種方法相比,這樣做的優點是效率比較高(不用執行和建立Animal的例項了),比較省記憶體。缺點是 Cat.prototype和Animal.prototype現在指向了同一個物件,那麼任何對Cat.prototype的修改,都會反映到Animal.prototype。

所以,上面這一段程式碼其實是有問題的。請看第二行;

Cat.prototype.constructor = Cat;

這一句實際上把Animal.prototype物件的constructor屬性也改掉了!

alert(Animal.prototype.constructor); // Cat

到這裡,大家可以和這篇部落格的第二部分的作一個對比,在第二部分中,實現繼承的關鍵程式碼是

   Cat.prototype = new Animal();
  Cat.prototype.constructor = Cat;

可以驗證的是,

alert(Animal.prototype.constructor)//Animal

這就是

Cat.prototype = new Animal();//prototype相當於一個指標,指向一個物件,這裡直接把new Animal()這個例項直接賦值給prototype指向的那個物件。

Cat.prototype = Animal.prototype;//這裡便形成了指標的重定向,Cat的prototype指向了Animal的prototype所指向的那個物件,

之間的區別!所以這也就是Animal.prototype.constructor的指向一個沒改變,一個改變了。

四、組合繼承(最常用)

給Animal增加一個屬性—-函式

function Animal(){
     this.species = "動物";
  }
Animal.prototype.fun = function(){};//給Animal增加一個函式

實現Cat的繼承,

function Cat(){
    Super.call(this);   // 核心
    this.name = name;
   this.color = color
}
Cat.prototype = new Animal();    // 核心
var cat1 = new Cat("大毛","黃色");
alert(cat1.species); // 動物
alert(sub1.fun === sub2.fun);   // true,說明sub1.fun和sub2.fun指向相同的地址,這便是組合繼承的優點,各種繼承方式的優缺點將在下一篇講

到此,我們常用的javascript繼承方式已經講完了,相信也有人說還有拷貝繼承的方式,但個人不太喜歡拷貝繼承,即使是做js專案,以上的繼承方式便足夠了,我相信在實際程式設計中,你也只會用一種繼承方式。

參考自阮一峰的部落格(另提一下,在阮一峰老師的這篇部落格裡的第四部分是有問題的,個人親測,第五部分好像也有問題,也不太常用,就不放在我的部落格裡了)