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屬性。

如果"貓"的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對象都有一個constructor屬性,指向它的構造函數。如果沒有"Cat.prototype = new

Animal();"這一行,Cat.prototype.constructor是指向Cat的;加了這一行以

后,Cat.prototype.constructor指向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生成的),因此我們必須手動糾正,將Cat.prototype對象的constructor值改為Cat。這就是第二行的意思。

這是很重要的一點,編程時務必要遵守。下文都遵循這一點,即如果替換了prototype對象,

o.prototype = {};

那么,下一步必然是為新的prototype對象加上constructor屬性,并將這個屬性指回原來的構造函數。

o.prototype.constructor = o;

三、 直接繼承prototype

第三種方法是對第二種方法的改進。由于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

四、 利用空對象作為中介

由于"直接繼承prototype"存在上述的缺點,所以就有第四種方法,利用一個空對象作為中介。

var F = function(){};

F.prototype = Animal.prototype;

Cat.prototype = new F();

Cat.prototype.constructor = Cat;

F是空對象,所以幾乎不占內存。這時,修改Cat的prototype對象,就不會影響到Animal的prototype對象。

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

我們將上面的方法,封裝成一個函數,便于使用。

function extend(Child, Parent) {

var F = function(){};

F.prototype = Parent.prototype;

Child.prototype = new F();

Child.prototype.constructor = Child;

Child.uber = Parent.prototype;

}

使用的時候,方法如下

extend(Cat,Animal);

var cat1 = new Cat("大毛","黃色");

alert(cat1.species); // 動物

這個extend函數,就是YUI庫如何實現繼承的方法。

另外,說明一點,函數體最后一行

Child.uber = Parent.prototype;

意思是為子對象設一個uber屬性,這個屬性直接指向父對象的prototype屬性。(uber是一個德語詞,意思是"向上"、"上一層"。)這等于在子對象上打開一條通道,可以直接調用父對象的方法。這一行放在這里,只是為了實現繼承的完備性,純屬備用性質。

五、 拷貝繼承

上面是采用prototype對象,實現繼承。我們也可以換一種思路,純粹采用"拷貝"方法實現繼承。簡單說,如果把父對象的所有屬性和方法,拷貝進子對象,不也能夠實現繼承嗎?這樣我們就有了第五種方法。

首先,還是把Animal的所有不變屬性,都放到它的prototype對象上。

function Animal(){}

Animal.prototype.species = "動物";

然后,再寫一個函數,實現屬性拷貝的目的。

function extend2(Child, Parent) {

var p = Parent.prototype;

var c = Child.prototype;

for (var i in p) {

c[i] = p[i];

}

c.uber = p;

}

這個函數的作用,就是將父對象的prototype對象中的屬性,一一拷貝給Child對象的prototype對象。

使用的時候,這樣寫:

extend2(Cat, Animal);

var cat1 = new Cat("大毛","黃色");

alert(cat1.species); // 動物

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 1.背景介紹 什么是面向對象編程? “面向對象編程”(Object OrientedProgramming,縮寫為...
    return_3711閱讀 276評論 0 0
  • 大家好,我是IT修真院北京分院25期的學員,一枚正直純潔善良的web前端程序員 今天給大家分享一下,修真院官網js...
    be684ac78b0c閱讀 271評論 0 0
  • 天涼氣侵骨,雨霧迷長路。 同行無一人,饑寒思故土。 2012.6.17
    森垚閱讀 484評論 0 0
  • 1.概括 ES5 的對象屬性名都是字符串,這容易造成屬性名的沖突。比如,你使用了一個他人提供的對象,但又想為這個對...
    不去解釋閱讀 533評論 0 1
  • 表示:頭大如虎,尾細如蛇。比喻開始時聲勢很大,到后來勁頭很小,有始無終,做事不始終如一。現在多指做事馬虎不認真。出...
    曾末閱讀 1,174評論 0 0