參考自:[http://jingyan.baidu.com/article/cd4c2979f2f664756e6e600b.html]
原型鏈繼承
利用prototype實現繼承,prototype對象是個模板,要實例的對象都是以這個模板爲基礎,它的任何屬性和方法都被傳遞給那個類的所有實例,原型鏈利用這種功能來實現繼承機制。
例子:
function ClassA(){}
ClassA.prototype.color="red";
ClassA.prototype.sayColor=function(){
alert(this.color);
}
function ClassB(){}
ClassB.prototype=newClassA();
通過原型鏈,ClassA的所有屬性和方法傳遞給了ClassB,用prototype的確是方便很多。
注意的是調用ClassA的構造函數是,沒有給它傳遞參數,這是在原型鏈中的標準做法。要確保構造函數沒有任何參數。如果構造函數中有參數的話會怎樣呢?那樣便不能完全的繼承,只能繼承父類通過prototype初始的屬性和方法,在構造函數中初始的屬性和方法便不會繼承。
與對象冒充相似,子類的所有屬性和方法都必須出現在prototype屬性被賦值之後,因爲在它之前賦值的所有方法都會被刪除。爲什麼呢?因爲prototype屬性被替換成了新的對象,原始對象的prototype屬性以不復存在了
對象冒充(構造函數繼承)
感覺這種方式利用了js中類和函數的模糊性,同是function關鍵字申明方法,既可以說他是函數,也可以說他是類,js太靈活了
例子:
function ClassA(sColor){
this.color=sColor;
this.sayColor=function(){
alert(this.color);
}
}
function ClassB(sColor){
this.newMethod=ClassA;//把ClassA方法賦給newMethod.
this.newMethod();//調用newMethod.
delete this.newMethod;
}
ClassB執行ClassA方法便相當於繼承了ClassA,在調用完畢後要刪除newMethod,因爲後續添加的屬性和方法如果和超類同名,就會覆蓋超類的相關屬性和方法。
利用這種繼承方式可以實現多重繼承,例如:
function ClassD(sColor){
this.newMethod=ClassA;//把ClassA方法賦給newMethod,
this.newMethod();//調用newMethod
delete this.newMethod;
this.newMethod=ClassB;
this.newMethod();
delete this.newMethod;}
利用這種多重繼承方法有個弊端,如果ClassA和ClassB具有相同的屬性和方法,ClassB有較高的優先級。
call方法和apply方法
ECMAScript在function對象加入兩個新方法,call()和apply(),這兩中方法很相似,只有在傳參方面有所不同,call()方法的第一個參數用作this的對象,例如:
function ClassB(sColor,sName){
ClassA.call(this,sColor);
this.name=sName;
this.sayName=function(){
alert(this.name);
}
}
call方法還是調用了ClassA()方法,利用this傳遞進去ClassB,爲ClassB初始化,他僅僅是調用了ClassA()方法而已,如果你在ClassA之外爲ClassA添加了方法(例如利用原型法),是不能繼承到ClassB的。call()方法的第一個參數必須是this,可以有第二個,第三個,第四個….參數。
apply()方法與call()方法不同的是,將二個,第三個,第四個….參數用一個數組傳遞。例如:
function ClassB(sColor,sName,sSex){
var arr=new Arry(sColor,sName,sSex);
ClassA.apply(this,arr);//傳遞數組
this.name=sName;
this.sayName=function(){
alert(this.name);
}
}
可以將arguments作爲參數傳遞給apply,但是ClassB的參數順序必須和ClassA一致。
另外,Function.apply()在提升程序性能方面有着突出的作用:
我們先從Math.max()函數說起,Math.max後面可以接任意個參數,最後返回所有參數中的最大值。
alert(Math.max(5,7,9,3,1,6)); //9
//但是在很多情況下,我們需要找出數組中最大的元素。
var arr=[5,7,9,1];
//alert(Math.max(arr)); // 這樣卻是不行的。NaN
//要這樣寫
function getMax(arr){
var arrLen=arr.length;
for(var i=0,ret=arr[0];i<arrLen;i++){
ret=Math.max(ret,arr[i]);
}
return ret;
}
alert(getMax(arr)); //9
//換用apply,可以這樣寫
function getMax2(arr){
return Math.max.apply(null,arr);
}
alert(getMax2(arr)); //9
//兩段代碼達到了同樣的目的,但是getMax2卻優雅,高效,簡潔得多。
//再比如數組的push方法。
var arr1=[1,3,4];
var arr2=[3,4,5];
//如果我們要把 arr2展開,然後一個一個追加到arr1中去,最後讓arr1=[1,3,4,3,4,5]
//arr1.push(arr2)顯然是不行的。 因爲這樣做會得到[1,3,4,[3,4,5]]
//我們只能用一個循環去一個一個的push(當然也可以用arr1.concat(arr2),但是concat方法並不改變arr1本身)
var arrLen=arr2.length;
for(var i=0;i<arrLen;i++){
arr1.push(arr2[i]);
}
//自從有了Apply,事情就變得如此簡單
Array.prototype.push.apply(arr1,arr2); //現在arr1就是想要的結果
混合方式
這種繼承方式使用構造函數定義類,並未使用任何原型,對象冒充的主要問題是必須使用構造函數方式,這不是最好的選擇。不過如果使用原型鏈,就無法使用帶參的構造函數了。開發者該如何選擇呢?答案很簡單,兩者都使用。創建類的最好方式是用構造函數方式定義屬性,用原型方式定義方法。這種方式同樣適用於繼承機制,用對象冒充繼承函數的屬性,用原型鏈繼承prototype對象的方法,用這兩種方式重寫前面的例子,代碼如下:
function ClassA(sColor){
this.color=sColor;
}
ClassA.prototype.sayColor=function(){
alert(this.color);
};
function ClassB(sColor,sName){
callA.call(this,sColor);
this.name=sName;
ClassB.prototype=new ClassA();
ClassB.prototype.sayName=function(){
alert(this.name);
};
在此例子中,繼承機制有兩行突出顯示的代碼實現。在第一行中,在ClassB的構造函數中,用對象冒充繼承ClassA類的sColor屬性。在第二行,用原型鏈繼承ClassA類的方法。由於這種混合方式使用了原型鏈,所以instanceof運算符仍能正確運行。