javascript雖然是一門面向對象的語言,可是它的繼承機制從一開始設計的時候就不一樣於傳統的其餘面嚮對象語言,是基於原型的繼承機制,可是在這種機制下,繼承依然有一些不一樣的實現方式。javascript
方法一:類式繼承java
所謂的類式繼承就是指模仿傳統面嚮對象語言的繼承方式,繼承與被繼承的雙方都是「類」,代碼以下:程序員
首先定義一個父類(或超類):設計模式
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,以下:this
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屬性,子類能夠經過該屬性調用父類,從而創建起了子類和父類的關係。spa
定義一個子類Author來繼承父類Person,以下:prototype
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,將其掛載到對應的原型上,達到了在繼承的基礎上進一步擴充自身的目的。設計
這種繼承方式很明顯就是採用相似於傳統面嚮對象語言的類式繼承,優勢是對習慣於傳統面向對象概念的程序員來說很容易理解,缺點是過程比較繁瑣,內存消耗稍大,由於子類也擁有本身的構造函數及原型,並且子類和父類的屬性徹底是隔離的,即便二者是同樣的值,可是不能共享同一段內存。code
方法二:原型式繼承
首先定義一個父類,這裏不會刻意模仿使用構造函數來定義,而是直接採用對象字面量的方式定義一個對象,該對象就是父類
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個摻元類用來擴充該類,它一樣可以繼承你定義的其餘摻元類的屬性和方法,這樣就實現了多重繼承,最後,author的serialize方法的運行結果以下:
你會發現該類同時具備person類,Author類,Mixin類的屬性和方法,其中Person和Mixin的屬性和方法都是經過「繼承」得來的,從實際上來說,它實現了多重繼承。