javascript中的繼承實現
javascript雖然是一門物件導向的語言,但是它的繼承機制從一開始設計的時候就不同於傳統的其他物件導向語言,是基於原型的繼承機制,但是在這種機制下,繼承依然有一些不同的實現方式。 方法一:類式繼承 所謂的類式繼承就是指模仿傳統物件導向語言的繼承方式,繼承與被繼承的雙方都是“類”,代碼如下: 首先定義一個父類(或超類): 1 function Person(name){2 this.name=name;3 }4 5 Person.prototype.getName=function(){6 return this.name;7 }; 該父類person的屬性在建構函式中定義,可以保證繼承它的子類的name屬性不與它共用這個屬性,而是單獨屬於子類,將getName方法掛載到原型上,是為了讓繼承它的子類的多個執行個體共用這一個方法體,這樣能夠節省記憶體,(對於多個執行個體來講,每次New一個執行個體出來都會保證這些執行個體的getName方法引用的是同一段記憶體空間,而不是獨立的空間)。 定義一個繼承方法extend,如下: function extend(subClass,superClass){ var F=function(){}; F.prototype=superClass.prototype; subClass.prototype=new F(); subClass.prototype.constructor=subClass; subClass.superClass=superClass.prototype; if(superClass.prototype.constructor==Object.prototype.constructor){ superClass.prototype.constructor=superClass; } } 在這個方法中,首先建立一個新的類為F,讓它的原型為父類的原型,並且讓子類的原型指向該類F的一個執行個體,從而達到了一個繼承父類的目的,同時,子類的原型由於被修改,所以將修改後的原型的constructor屬性指向子類,讓其擁有建構函式,同時給該子類掛載一個superClass屬性,子類可以通過該屬性調用父類,從而建立起了子類和父類的關係。 定義一個子類Author來繼承父類Person,如下: 1 function Author(name,books){2 Author.superClass.constructor.call(this,name);3 this.book=books;4 }5 extend(Author,Person);6 Author.prototype.getBooks=function(){7 return this.book;8 } 這裡在子類的建構函式中通過其superClass屬性調用父類的建構函式,同時採用call方法,轉換該方法調用的this指向,讓子類Author也擁有(繼承)父類的屬性,同時子類又擁有自己的屬性book,所以在建構函式中將參數books賦值給屬性book,達到構造的目的。採用extend函數繼承父類Person原型上的屬性和方法(實際上只繼承了方法,因為我們之前只是將方法掛載到了原型上,屬性是在建構函式中定義的)。同時,Author又擁有自己的方法getBooks,將其掛載到對應的原型上,達到了在繼承的基礎上進一步擴充自身的目的。 這種繼承方式很明顯就是採用類似於傳統物件導向語言的類式繼承,優點是對習慣於傳統物件導向概念的程式員來講很容易理解,缺點是過程比較繁瑣,記憶體消耗稍大,因為子類也擁有自己的建構函式及原型,而且子類和父類的屬性完全是隔離的,即使兩者是一樣的值,但是不能共用同一段記憶體。 方法二:原型式繼承 首先定義一個父類,這裡不會刻意模仿使用建構函式來定義,而是直接採用對象字面量的方式定義一個對象,該對象就是父類 1 var Person={2 name:'default name',3 getName:function(){4 return this.name;5 }6 7 } ; 和第一種方法一樣,該對象擁有一個屬性name和一個方法getName . 然後再定義一個複製方法,用來實現子類對父類的繼承,如下: 1 function clone(obj){2 function F(){}3 F.prototype=obj;4 return new F();5 }該複製方法建立一個對象,將該對象的原型指向父類即參數obj,同時返回這個對象。 最後子類再通過複製函數繼承父類,如下: 1 var Author=clone(Person);2 Author.book=['javascript'];3 Author.showBook=function(){4 return this.book;5 }這裡定義一個子類,通過clone函數繼承父類Person,同時拓展了一個屬性book,和一個方法showBook,這裡該子類也擁有屬性name,但是它和父類的name值是一樣的,所以沒有進行覆蓋,如果不一樣,可以採用 Author.name='new name';覆蓋這個屬性,從而得到子類的一個新的name屬性值。 這種原型式繼承相比於類式繼承更為簡單自然,同時如果子類的屬性和父類屬性值相同,可以不進行修改的話,那麼它們兩者其實共用的是同一段記憶體空間,如上面的name屬性,缺點是對於習慣了傳統物件導向的程式員難以理解,如果兩者要進行選擇的話,無疑是這種方式更為優秀一些。 既然javascript中採用基於原型的方式來實現繼承,而且每個對象的原型只能指向某個特定的類的執行個體(不能指向多個執行個體),那麼如何?多重繼承(即讓一個類同時具有多個類的方法和屬性,而且本身內部不自己定義這些方法和屬性)? 在javascript設計模式中給出了一種摻元類(mixin class)的方式: 首先定義一個摻元類,用來儲存一些常用的方法和屬性,這些方法和屬性可以通過拓展的方式添加到任何其他類上,從而被添加類就具有了該類的某些方法和屬性,如果定義多個摻元類,同時添加給一個類,那麼該類就是間接實現了“多重繼承”,基於這種思想,實現如下: 摻元類定義: 1 var Mixin=function(){}; 2 Mixin.prototype={ 3 serialize:function(){ 4 var output=[]; 5 for(key in this){ 6 output.push(key+":"+this[key]); 7 } 8 return output.join(','); 9 }10 } 該摻元類具有一個serialize方法,用來遍曆其自身,輸出自身的屬性和屬性值,並且將他們以字串形式返回,中間用逗號隔開。 定義一個擴充方法,用來使某個類經過擴充之後具備摻元類的屬性或方法,如下: 1 function augment(receivingClass,givingClass){ 2 if(arguments[2]){ 3 for(var i= 2,len=arguments.length;i<len;i++){ 4 receivingClass.prototype[arguments[i]]=givingClass.prototype[arguments[i]]; 5 } 6 } 7 else 8 { 9 for(methodName in givingClass.prototype){10 if(!receivingClass.prototype[methodName]){11 receivingClass.prototype[methodName]=givingClass.prototype[methodName];12 }13 }14 15 }16 } 該方法預設是兩個參數,第一個參數是接受被擴充的類,第二個參數是摻元類(用來擴充其他類的類),還可以有其他參數,如果大於兩個參數,後面的參數都是方法或者屬性名稱,用來表示被擴充類只想繼承摻元類的指定的屬性或方法,否則預設繼承摻元類的所有屬性和方法,在該函數中,第一個if分支是用來繼承指定屬性和方法的情形,else分支是預設繼承所有屬性和方法的情形。該方法的實質是將摻元類的原型上的屬性和方法都擴充(添加)到了被擴充類的原型上面,從而使其具有摻元類的屬性和方法。 最後,使用擴充方法實現多重繼承 1 augment(Author,Mixin);2 var author= new Author('js',['javascript design patterns']);3 alert(author.serialize());這裡定義了一個author的類,該類繼承自Person父類,同時又具備摻元類Mixin的方法和屬性,如果你願意,可以定義N個摻元類用來擴充該類,它同樣能夠繼承你定義的其他摻元類的屬性和方法,這樣就實現了多重繼承