讀古今文學網 > 編寫高質量代碼:改善JavaScript程序的188個建議 > 建議96:避免使用複製繼承 >

建議96:避免使用複製繼承

複製繼承是最原始的方法,其設計思路:利用for in語句遍歷對像成員,然後逐一將其複製給另一個對象,通過這種「螞蟻搬家」的方式來實現繼承關係。例如,在下面的示例中,先定義一個F類,它包含4個成員,然後將其實例化並把它的所有屬性和方法都複製給一個空對像o,這樣對像o就擁有了F類的所有屬性和方法。


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

this.x=x;

this.y=y;

this.add=function{

return this.x+this.y;

}

}

F.prototype.mul=function{

return this.x*this.y;

}

var f=new F(2,3)

var o={}

for(var i in f){//遍歷構造函數的實例,把它的所有成員都賦值給對像o

o[i]=f[i];

}

alert(o.x);//2

alert(o.y);//3

alert(o.add);//5

alert(o.mul);//6


對於該複製繼承法,可以將其封裝,使其具有較大的靈活性。


Function.prototype.extend=function(o){//為Function擴展複製繼承的方法

for(var i in o){

this.constructor.prototype[i]=o[i];//把參數對像成員複製給當前對象的構造函數原型對像

}

}


上面的封裝函數通過原型對像為Function核心對像擴展一個方法,該方法能夠把指定的參數對像完全複製給當前對象的構造函數的原型對象。this關鍵字指向的是當前實例對象,而不是構造函數本身,所以要為其擴展原型成員,就必須使用constructor屬性來指向它的構造器,然後通過prototype屬性指向構造函數的原型對象。

接下來,新建一個空的構造函數,並為其調用extend方法,把傳遞進來的F類的實例對像完全複製為原型對像成員。注意,此時就不能夠定義對像直接量,因為extend方法只能為構造函數複製繼承:


var o=function{};

o.extend(new F(2,3));


複製繼承法也不是真正的繼承,它是通過反射機制複製類對象的所有可枚舉屬性和方法來模擬繼承。這種方法能夠實現模擬多繼承。不過,它的缺點也很明顯:

❑由於是反射機制,複製繼承法不能繼承非枚舉類型的方法,系統核心對象的只讀方法和屬性也是無法繼承的。

❑通過反射機制來複製對像成員的執行效率會非常差。對像結構越龐大,這種低效就表現得越明顯。

❑如果當前類型包含同名成員,那麼這些成員可能會被父類的動態複製所覆蓋。

❑在多重繼承的情況下,複製繼承不能夠清晰地描述父類與子類的相關性。

❑只有在類被實例化後,才能夠實現遍歷成員和複製成員,因此它不能夠靈活支持動態參數。

❑由於複製繼承法僅是簡單地引用賦值,如果父類的成員值包含引用類型,那麼用複製繼承法繼承後,與原型繼承法一樣副作用很多。

還可以對複製繼承法進行適當優化,通過對像克隆方式來實現,這樣就可以避免一個個複製對像成員所帶來的低效率,具體方法如下:

首先,為Function對像擴展一個方法,該方法能夠把參數對像賦值給一個空構造函數的原型對象,然後實例化構造函數並返回實例對象,這樣該對象就擁有構造函數包含的所有成員,例如:


Function.prototype.clone=function(o){//對像克隆方法

function Temp{};//新建空構造函數

Temp.prototype=o;//把參數對像賦值給該構造函數的原型對像

return new Temp;//實例化後的對象

}


然後,調用該方法來克隆對象。克隆方法返回的是一個空對象,不過它存儲了指向給定對象的原型對像指針,這樣就可以利用原型鏈來訪問這些變量,從而在不同對像之間實現繼承關係。例如:


var o=Function.clone(new F(2,3));//調用Function對象的克隆方法

alert(o.x);//2

alert(o.y);//3

alert(o.add);//5

alert(o.mul);//6