物件導向語言有兩種繼承方式:介面繼承(只繼承方法名);實現繼承(繼承實際的方法)。但在ECMAScript中,函數名沒多大含義,只是函數體的引用而已,因此,ECMAScript無法實現介面繼承,只支援實現繼承。實現繼承,主要是依靠原型鏈來完成的。
一、原型鏈 1.原型鏈的基本思想是:利用原型讓一個參考型別繼承另一個參考型別的屬性和方法。
2.建構函式、原型、執行個體之間的關係
(1)每個建構函式都有一個原型對象,Func.prototype指向了這個原型對象。
(2)原型對象都包含一個指向建構函式的指標,Func.prototype.constructor等於Func,可以理解為prototype和constructor是兩個方向相反的指標,在建構函式和原型之間搭建起橋樑。
(3)每個執行個體,都包含一個指向原型對象的內部指標__proto__,當然這個對開發人員是不可見的。
(4)如果把建構函式A的原型,設成建構函式B的一個執行個體,那麼建構函式A的原型裡就包含了指向另一個原型的指標,從而形成了原型鏈。這樣就實現了繼承。使用對象的屬性或方法時,解析器先搜尋執行個體內部,找不到就搜尋執行個體的原型內部,再找不到就搜尋執行個體的原型的建構函式的原型內部,一級一級向上尋找。
//小實驗function Human(){}Human.prototype.getSex=function(){return this.sex;}Human.prototype.setSex=function(v){this.sex=v;}function Male(){this.setSex("male");}Male.prototype=new Human();Male.prototype.getAge=function(){return this.age;}Male.prototype.setAge=function(v){this.age=v;}var tom=new Male();tom.setAge(20);alert(tom.getSex());//malealert(tom.getAge());//20alert(tom instanceof Male);//truealert(tom instanceof Human);//truealert(tom instanceof Object);//truealert(tom.constructor == Male);//falsealert(tom.constructor == Human);//true(5)原型鏈的最頂端是Object,這就是為什麼所有參考型別instanceof Object都會返回true。換句話說,只要instanceof後面的建構函式在執行個體的原型鏈中,都會返回true。
(6)注意,由於繼承的時候,原型被重寫了,所以tom的constructor指像的不再是Male,而是Male.prototype的建構函式Human。
3.使用原型鏈做繼承的問題。
(1)和原型模式建立對象一樣,使用原型鏈做繼承,也會遇到多個執行個體共用object(參考型別)屬性的情況。這也就是為啥參考型別屬性要放在建構函式內部。
(2)更嚴重一個問題是,使用原型鏈做繼承時,必須用基類的執行個體去替換衍生類別的原型,產生基類的執行個體的過程是不能使用參數的,這就大大降低了繼承的靈活性。
二、借用建構函式 1.為瞭解決原型鏈做繼承遇到的第一個問題,出現了借用建構函式的方法。思想是把基類的建構函式當作普通函數執行一次,但執行的時候,必須把衍生類別的執行空間傳過去。
//小實驗function Human(){this.sex="Please input";this.age=-1;}function Male(){Human.call(this);//顯示地把基類建構函式當作普通函數調用。}Male.prototype=new Human();var tom=new Male();var jim=new Male();tom.sex="male";alert(tom.sex);//malealert(jim.sex);//Please input2.當然,構造時不傳遞參數用處還是不大,幸好apply和call都是支援傳參的。
//小實驗function Human(sex,age){this.sex=sex;this.age=age;}function Male(age){Human.apply(this,["male",age]);}Male.prototype=new Human();var tom=new Male(30);var jim=new Male(25);alert(tom.sex);//malealert(jim.age);//25alert(tom.age);//303.借用建構函式解決了參考型別屬性的問題,但沒有解決方案的問題。
三、組合繼承(偽經典繼承) 1.這種繼承是把原型鏈和借用建構函式結合起來。用借用建構函式處理屬性,用原型鏈處理方法。其實就相當於建立對象時提到的“組合模式”
//小實驗function Human(sex){this._sex=sex;}Human.prototype.getSex=function(){return this._sex;}Human.prototype.setSex=function(v){this._sex=v;}function Male(name,age){Human.call(this,"male");this.name=name;this.age=age;}Male.prototype=new Human();Male.prototype.sayHello=function(){alert("Hello, my name is "+this.name+", I'm "+this.age+" years old, I'm a "+(this.getSex()=="male"?"boy":"girl")+".");}var tom=new Male("tom",15);var jan=new Male("jan",13);jan.setSex("female");tom.sayHello();jan.sayHello();2.這是目前最常用,也是解決最完美的繼承寫法了,沒有之一。