讀古今文學網 > 編寫高質量代碼:改善JavaScript程序的188個建議 > 建議93:推薦使用類繼承 >

建議93:推薦使用類繼承

類繼承也稱為構造函數繼承,還稱為對像模擬法。其表現形式:在子類中執行父類的構造函數。其實現本質:構造函數也是函數,與普通函數相比,它只不過是一種特殊結構的函數而已。可以將一個構造函數(如A)的方法賦值為另一個構造函數(如B),然後調用該方法,使構造函數A在構造函數B內部被執行,這時構造函數B就擁有在構造函數A中定義的屬性和方法,這就是所謂B類繼承A類。下面看一個示例:


function A(x){//構造函數A

this.x=x;

this.say=function{

alert(this.x);

}

}

function B(x,y){//構造函數B

this.m=A;//把構造函數A作為一個普通函數引用給臨時方法m

this.m(x);//把當前構造函數參數x作為值傳遞給構造函數A,並執行

delete this.m;//清除臨時方法

this.y=y;

this.call=function{

alert(this.y);

}

}

var a=new A(1);

var b=new B(2,3);

a.say;//調用實例化A的方法say,返回1

b.say;//在B類中調用A類的方法say,返回2,說明繼承成功

b.call;//調用實例化B的方法call,返回3


構造函數能夠使用this關鍵字為所有屬性和方法賦值。在默認情況下,關鍵字this引用的是構造函數當前創建的對象。不過在這個方法中,this不是指向當前正在使用的實例對象,而是調用構造函數的方法所屬的對象,即構造函數B。此時,構造函數A已經不是構造函數了,而被視為一個普通可執行函數。

上面的示例演示了類繼承的實現基礎。實際上,在複雜的編程中,是不會使用上面方法來定義類繼承的,因為它的設計模式太隨意,缺乏嚴密性。嚴謹的設計模式應該考慮到各種可能存在的情況和類繼承關係中的相互耦合性。為了更直觀地說明,先看一個示例:


function A(x){//構造函數A

this.x=x;

}

A.prototype.getx=function{

return this.x;

}


在上面的代碼中,先創建一個構造函數,它相當於一個類,類名是構造函數的名稱A。在結構體內使用this關鍵字創建本地屬性x。方法getx被放在類的原型對像中成為公共方法。然後,借助new運算符調用構造函數,返回的是新創建的對象實例:


var a1=new A(1);


最後,對像a1就可以繼承類A的本地屬性x,也有人稱之為實例屬性,當然還可以訪問類A的原型方法getx:


alert(a1.x);//繼承類A的屬性x

alert(a1.getx);//引用類A的方法getx


上面的代碼是一個簡單的類的演示。現在,創建一個類B,讓其繼承類A,實現的代碼如下:


function B(x,y){//構造函數B

this.y=y;

A.call(this,x);//在構造函數B中調用超類A,實現綁定

}

B.prototype=new A;//設置原型鏈,建立繼承關係

B.prototype.constructor=B;//恢復B的原型對象的構造函數為B

B.prototype.gety=function{

return this.y;

}


在構造函數B的結構體內,使用函數call調用構造函數A,把B的參數x傳遞給調用函數。讓B能夠繼承A的所有屬性和方法,即執行「A.call(this,x);」語句行。在構造函數A和B之間建立原型鏈,即執行「B.prototype=new A;」語句行。恢復B的原型對象的構造函數,即執行「B.prototype.constructor=B;」語句行。當定義構造函數時,其原型對像(prototype屬性值)默認是一個Object類型的一個實例,其構造器(constructor屬性值)會被默認設置為該構造函數本身。如果改動prototype屬性值,使其指向另一個對象,那麼新對象就不會擁有原來的constructor屬性值,所以必須重新設置constructor屬性值。

此時,就可以在子類B的實例對像中調用超類A的屬性和方法了。


var f2=new B(10,20);

alert(f2.getx);//10

alert(f2.gety);//20


最後,看一個更複雜的多重繼承的實例。


//基類A

function A(x){

this.getl=function{

return x;

}

}

A.prototype.has=function{

return!(this.getl==0);

}

//超類B

function B{

var a=;

a=Array.apply(a,arguments);

A.call(this,a.length);

this.add=function{

return a.push.apply(a,arguments);

}

this.geta=function{

return a;

}

}

B.prototype=new A;//建立原型鏈

B.prototype.constructor=B;//恢復構造器

B.prototype.str=function{

return this.geta.toString;

}

//子類C

function C{

B.apply(this,arguments);//在當前對像中調用B類

this.sort=function{

var a=this.geta;

a.sort.apply(a,arguments);

}

}

C.prototype=new B;//建立原型鏈

C.prototype.constructor=C;//恢復C類原型對象的構造器

//超類B的實例繼承類A的成員

var b=new B(1,2,3,4);

alert(b.getl);//4

alert(b.has);//true

//子類C的實例繼承類B和類A的成員

var c=new C(30,10,20,40);

c.add(6,5);

alert(c.geta)//數組30,10,20,40,6,5

c.sort//排序數組

alert(c.geta)//數組10,20,30,40,5,6

alert(c.getl)//4

alert(c.has);//true

alert(c.str);//10,20,30,40,5,6


在上面的示例代碼中,設計類C繼承類B,而類B又繼承了類A。A、B、C三個類之間的繼承關係是通過在子類中調用父類的構造函數來維護的。例如,在C類中添加「B.apply(this,arguments);」語句,該行語句能夠在B類中調用A類,並且把B的參數傳遞給A,從而使B類擁有A類的所有成員。同理,在B類中添加「A.call(this,a.length);」語句,該行語句把B類的參數長度作為值傳遞給A類,並進行調用,從而實現B類擁有A類的所有成員。

從繼承關係上看,B類繼承了A類的本地方法getl。為了確保B類還能夠繼承A類的原型方法,還需要為它們建立原型鏈,從而實現原型對象的繼承關係,方法是添加語句行「B.prototype=new A;」。同理,在C類中添加語句行「C.prototype=new B;」,這樣就可以把A、B和C三個類通過原型鏈連在一起,從而實現子類能夠繼承超類成員,甚至還可以繼承基類成員。這裡的成員主要指類的原型對像包含的成員,當然它們之間也可以通過相互調用來實現對本地成員的繼承關係。

注意原型繼承中的先後順序,在為B類的原型指定A類的實例前,不能再為其定義任何原型屬性或方法,否則就會被覆蓋。如果要擴展原型方法,就只有在進行原型綁定之後,再定義擴展方法。