javascript
javascript 学习笔记之面向对象编程(二):继承多态
~~接上篇~~上一篇實現(xiàn)了類的實現(xiàn)以及類成員變量和方法的定義,下面我們來了解下面向?qū)ο笾袃蓚€最重要的特性:繼承和多態(tài)。
繼承
js中同樣可以實現(xiàn)類的繼承這一面向?qū)ο筇匦?#xff0c;繼承父類中的所有成員(變量和屬性),同時可擴展自己的成員,下面介紹幾種js中實現(xiàn)繼承的方式:
1. 對象模仿(冒充):通過動態(tài)改變this指針的指向,實現(xiàn)對父類成員的重復(fù)定義,如下:?
1 //對象冒充 2 function ClassA(paramColor) { 3 this.color = paramColor; 4 this.sayColor = function() { 5 alert(this.color); 6 }; 7 } 8 9 function ClassB(paramColor, name) { 10 //冒充并實現(xiàn)ClassA中的成員 11 this.newMethod = ClassA; 12 this.newMethod(paramColor); 13 //刪除掉對ClassA類冒充所使用的函數(shù)對象。 14 delete this.newMethod; 15 16 this.name = name; 17 this.sayName = function() { 18 alert(this.name); 19 }; 20 } 21 22 var obj = new ClassB("yellow", "apple"); 23 24 console.log("實例obj是否是ClassA的對象" + (obj instanceof ClassA)); 25 console.log("實例obj是否是ClassB的對象" + (obj instanceof ClassB));上例中我們實現(xiàn)了兩個類,ClassA和ClassB,在ClassB的實現(xiàn)過程中,定義了一個函數(shù)newMethod來引用ClassA的構(gòu)造函數(shù)并執(zhí)行,這樣就等于執(zhí)行了A的構(gòu)造函數(shù),只不過此時的this指針指向的是類ClassB,故ClassA構(gòu)造函數(shù)的這個模仿執(zhí)行過程其實是給ClassB定義了相同的成員,最后刪除這個起橋梁性質(zhì)的冒充函數(shù),執(zhí)行結(jié)果如下:
根據(jù)執(zhí)行結(jié)果我們可以看出,子類ClassB定義的對象并不同屬其父類的實例,這種方式實現(xiàn)的繼承并不是實際意義上的繼承,?此外,這種方式只能模仿實現(xiàn)父類構(gòu)造函數(shù)中定義的成員,對于父類中通過prototype定義的成員將不能繼承。
2. 利用apply和call方法實現(xiàn)繼承:同第一種方式相似,這種方式是通過apply和call方法動態(tài)改變this指針的引用實現(xiàn)對父類成員的重復(fù)定義,下面對ClassB改寫如下:
1 //call方法 2 function ClassBEx(paramColor, name) { 3 ClassA.call(this, paramColor); 4 5 this.name = name; 6 this.sayName = function() { 7 alert(this.name); 8 } 9 } 10 //aply方法 11 function ClassBEEx(paramColor, name) { 12 //如果類A的構(gòu)造函數(shù)與類B的構(gòu)造函數(shù)參數(shù)順序完全相同時可用 13 ClassA.apply(this, arguments); 14 15 this.name = name; 16 this.sayName = function() { 17 alert(this.name); 18 } 19 }這種方式同上一種的優(yōu)缺點一樣,并不是實際意義上的繼承。
3. 共享prototype對象實現(xiàn)繼承:子類通過對父類prototype對象進(jìn)行共享以對父類成員的定義,從而實現(xiàn)繼承,下面對ClassA和ClassB進(jìn)行重新定義:
1 //類ClassA的定義 2 function ClassA(paramColor) { 3 this.color = paramColor; 4 } 5 ClassA.prototype.sayColor = function() { 6 console.log("執(zhí)行ClassA中的成員函數(shù)sayColor:" + this.color); 7 } 8 //類ClassB的定義 9 function ClassB(paramColor, name) { 10 this.name = name; 11 } 12 //類ClassB共享使用類ClassA的prototype對象 13 ClassB.prototype = ClassA.prototype; 14 ClassB.prototype.sayName = function() { 15 console.log(this.name); 16 } 17 //ClassB重寫了類ClassA中的函數(shù)成員 18 ClassB.prototype.sayColor = function() { 19 console.log(this.color); 20 } 21 22 var objA = new ClassA("yellow"); 23 var obj = new ClassB("red","apple"); 24 25 console.log("實例obj的color屬性" + obj.color); 26 console.log("實例obj是否是ClassA的對象" + (obj instanceof ClassA)); 27 console.log("實例obj是否是ClassB的對象" + (obj instanceof ClassB)); 28 objA.sayColor();上面陰影部分代碼實現(xiàn)子類ClassB對父類ClassA的prototype對象進(jìn)行共享,執(zhí)行結(jié)果如下:
結(jié)果有點點意外,可以總結(jié)為以下幾點:
1. 共享prototype對象可以實現(xiàn)子類的實例同屬于父類的實例,這點可通過 instance of 返回為true看出;
2. 這種方式的繼承只能繼承父類prototype中的定義的父類成員,對于父類構(gòu)造函數(shù)中的成員則不能繼承,如上圖:子類實例obj的color屬性為undefined。
3. 共享原型(prototype)法,實際上是使父類和子類的都引用同一個prototype對象,js中除了基本數(shù)據(jù)類型(數(shù)值、字符串、布爾類等),所有的賦值都是引用傳遞,而不是值傳遞,上述的共享導(dǎo)致ClassA和ClassB的prototype對象始終保持一致,所以當(dāng)子類ClassB重復(fù)定義了父類中的sayColor函數(shù)后,父類中的sayColor也同樣更新了,故調(diào)用父類sayColor后輸出的是“red”。
4. 共享原型方法會導(dǎo)致基類和派生類定義自己的成員時互相干擾。
總之,此方法還是不能實現(xiàn)實際意義上的繼承。
4. 通過反射機制和prototype實現(xiàn)繼承:在共享原型的基礎(chǔ)上進(jìn)行了改進(jìn),通過遍歷基類的原型對象來給派生類原型對象賦值,以達(dá)到繼承的目的,具體如下:
1 //類ClassA的定義 2 function ClassA(paramColor) { 3 this.color = paramColor; 4 } 5 ClassA.prototype.sayColor = function() { 6 console.log("執(zhí)行ClassA中的成員函數(shù)sayColor:" + this.color); 7 } 8 //類ClassB的定義 9 function ClassB(paramColor, name) { 10 this.name = name; 11 } 12 //遍歷基類的原型對象來給自己的原型賦值 13 for (var p in ClassA.prototype) { 14 ClassB.prototype[p] = ClassA.prototype[p]; 15 } 16 ClassB.prototype.sayName = function() { 17 console.log(this.name); 18 } 19 //ClassB重寫了類ClassA中的函數(shù)成員 20 ClassB.prototype.sayColor = function() { 21 console.log("執(zhí)行ClassB中的成員函數(shù)sayColor:red"); 22 } 23 24 var objA = new ClassA("yellow"); 25 var obj = new ClassB("red", "apple"); 26 27 console.log("實例obj的color屬性" + obj.color); 28 console.log("實例obj是否是ClassA的對象" + (obj instanceof ClassA)); 29 console.log("實例obj是否是ClassB的對象" + (obj instanceof ClassB)); 30 objA.sayColor(); 31 obj.sayColor();上面陰影部分的代碼為遍歷基類(ClassA)的prototype對象然后賦值給派生類ClassB的prototype對象,實現(xiàn)對基類的成員進(jìn)行繼承,執(zhí)行結(jié)果如下:
由上圖可見,基類和派生類的prototype是獨立的,派生類繼承了基類prototype定義的成員,并添加和重寫了基類的成員函數(shù)sayColor,它們的執(zhí)行結(jié)果互不干擾,唯一的缺憾是當(dāng)前這種方式仍然不能繼承基類構(gòu)造函數(shù)中定義的成員,這一點可以通過在派生類的構(gòu)造函數(shù)中添加一行代碼實現(xiàn),改寫派生類ClassB的定義如下:
1 //類ClassB的定義 2 function ClassB(paramColor, name) { 3 ClassA.call(this, paramColor); 4 this.name = name; 5 }這樣將基類的構(gòu)造函數(shù)通過this指針附加到派生類的執(zhí)行上下文中執(zhí)行,實現(xiàn)對基類構(gòu)造函數(shù)中定義的成員的繼承。
為了提高代碼的可讀性,我們改進(jìn)遍歷基類prototype的實現(xiàn)過程:?
1 Function.prototype.inherit = function(superClass) { 2 for (var p in superClass.prototype) { 3 this.prototype[p] = superClass.prototype[p]; 4 } 5 }通過給Function對象添加成員方法,我們給所有的函數(shù)類型對象添加了一個靜態(tài)方法,實現(xiàn)類的繼承我們可以通過下面這句代碼:
1 ClassB.inherit(ClassA);從繼承的角度,上面這種方式更加容易被接受,但是有一點,通過反射(遍歷)結(jié)合prototype實現(xiàn)繼承的派生類,如果需要額外定義自己的成員,則只能通過對ptototype對象定義新的屬性(ClassB.prototype.newAttr=?)來實現(xiàn),而不能通過無類型方式(ClassB.prototype={}),否則會覆蓋掉從基類繼承下來的成員。
5. 繼承的優(yōu)化:主要對最后一種繼承機制進(jìn)行優(yōu)化,定義一個Extend函數(shù),實現(xiàn)對從基類繼承后的對象的一個擴展,從而使得派生類添加新成員時更加高效,代碼實現(xiàn)如下:
1 /* 2 * 將對象p中的屬性全部添加到o對象中,如果存在重復(fù),則直接覆蓋 3 */ 4 function extend(o, p) { 5 for (prop in p) { 6 o[prop] = p[prop]; 7 } 8 return o; 9 } 10 /* 11 * 創(chuàng)建以o對象為原型的新的對象。 12 * 新的對象包含o中所有的成員 13 */ 14 function inherit(o) { 15 if (o == null) throw TypeError(); 16 if (Object.create) { 17 return Object.create(o); 18 } 19 var t = typeof p; 20 if (t !== "Object" && t !== "function") throw TypeError(); 21 function f() { } 22 f.prototype = o; 23 return new f(); 24 } 25 /* 26 * 通過Function給每個函數(shù)對象添加一個靜態(tài)方法 27 * constructor:派生類構(gòu)造函數(shù) 28 * methods:派生類需要新定義的成員方法 29 * statics:派生類需要定義的靜態(tài)變量或方法的集合 30 * 返回派生類構(gòu)造函數(shù) 31 */ 32 Function.prototype.extend = function(constructor, methods, statics) { 33 return definedSubClass(this, constructor, methods, statics); 34 } 35 /* 36 * js類繼承的核心方法 37 * superClass:基類的構(gòu)造函數(shù)(extend的執(zhí)行時this指針,執(zhí)行函數(shù)對象本身) 38 * constructor:派生類構(gòu)造函數(shù) 39 * methods:派生類需要新定義的成員方法 40 * statics:派生類需要定義的靜態(tài)變量或方法的集合 41 * 返回派生類構(gòu)造函數(shù) 42 */ 43 function definedSubClass(superClass, constructor, methods, statics) { 44 constructor.prototype = inherit(superClass.prototype); 45 constructor.prototype.constructor = constructor; 46 if (methods) extend(constructor.prototype, methods); 47 if (statics) extend(cosntructor, statics); 48 return constructor; 49 }這些都是實現(xiàn)類繼承模板的核心函數(shù),主要是通過Function對象給所有的函數(shù)類型的對象添加了一個靜態(tài)函數(shù),有了上面的函數(shù),實現(xiàn)上面ClassB繼承ClassA,我們可以改為成:
1 //類ClassA的定義 2 function ClassA(paramColor) { 3 this.color = paramColor; 4 } 5 ClassA.prototype.sayColor = function() { 6 console.log("執(zhí)行ClassA中的成員函數(shù)sayColor:" + this.color); 7 } 8 9 //ClassA作為基類派生出ClassB 10 var ClassB = ClassA.extend(function(paramColor, name) { 11 //構(gòu)造函數(shù)(成員屬性由構(gòu)造函數(shù)定義) 12 ClassA.call(this, paramColor); 13 this.name = name; 14 }, { 15 //新定義或者重新定義的方法 16 sayName: function() { 17 console.log(this.name); 18 }, 19 sayColor: function() { 20 console.log("執(zhí)行ClassB中的成員函數(shù)sayColor:red"); 21 } 22 }, 23 { 24 //無靜態(tài)成員 25 }); 26 27 var objA = new ClassA("yellow"); 28 var obj = new ClassB("red", "apple"); 29 30 console.log("實例obj的color屬性" + obj.color); 31 console.log("實例obj是否是ClassA的對象" + (obj instanceof ClassA)); 32 console.log("實例obj是否是ClassB的對象" + (obj instanceof ClassB)); 33 objA.sayColor(); 34 obj.sayColor();陰影部分,我們通過擴展的extend函數(shù)實現(xiàn)了類的繼承,簡單明了,執(zhí)行上面的例子,結(jié)果如下:
可以看出,優(yōu)化后的方法完美的實現(xiàn)了js類的繼承中遇到的幾個問題。
多態(tài)
面向?qū)ο缶幊讨械亩鄳B(tài)主要是通過抽象類和抽象函數(shù)實現(xiàn)的,js中也可以從這兩個方面實現(xiàn)多態(tài)。傳統(tǒng)意義上的多態(tài),是通過派生類繼承并實現(xiàn)基類中的抽象(虛)函數(shù)來實現(xiàn)的,含有抽象函數(shù)的類是抽象類,抽象類是不能夠?qū)嵗?#xff0c;同時,抽象函數(shù)沒有函數(shù)體,也不能夠直接調(diào)用,只能有派生類繼承并實現(xiàn)。在高級程序語言中,上述這些檢測均在程序編譯時進(jìn)行,不符合要求的程序編譯將不通過,但是在js中,有了些許變化:
1. js是解釋性語言,不需要進(jìn)行預(yù)編譯,所以js中抽象類和抽象函數(shù)的使用并沒有那么嚴(yán)格的要求。
2. js中可以對未定義的方法進(jìn)行調(diào)用,當(dāng)然這一過程會報錯,而檢測時在執(zhí)行調(diào)用時進(jìn)行的。
所以,js中的抽象類可以定義實例,但就其意義而言,我們可以定義一個空的沒有成員的類來代替,同樣,js中的抽象函數(shù),我們可以不必在基類中聲明,直接進(jìn)行調(diào)用,在派生類中實現(xiàn)即可,當(dāng)然,也可以通過在基類中定義一個空的抽象方法實現(xiàn),代碼如下:
1 function ClassA() { 2 //抽象類,類的實現(xiàn)過程為空 3 } 4 ClassA.prototype = { 5 sayColor: function() { 6 //直接調(diào)用抽象方法 7 this.initial(); 8 }, 9 //定義一個空的抽象方法由派生類去實現(xiàn),也可以不定義 10 initial: function() { } 11 } 12 13 //ClassA作為基類派生出ClassB 14 var ClassB = ClassA.extend(function(name) { 15 this.name = name; 16 }, { 17 //實現(xiàn)基類中的抽象方法 18 initial: function() { 19 console.log(this.name); 20 } 21 }, 22 { 23 //無靜態(tài)成員 24 });這樣的實現(xiàn)與真正意義上的多態(tài)相差有點大,可能會讓人疑惑這種必要性,為了最大程度的滿足嚴(yán)格意義上的多態(tài),我們改寫上面的代碼如下:
1 //抽象類 2 function ClassA() { throw new Error("can't instantiate abstract classes."); } 3 ClassA.prototype = { 4 initial: function() { throw new Error("can't call abstract methods."); } 5 } 6 7 //ClassA作為基類派生出ClassB 8 var ClassB = ClassA.extend(function(name) { 9 this.name = name; 10 }, { 11 //實現(xiàn)基類中的抽象方法 12 initial: function() { 13 console.log(this.name); 14 } 15 }, 16 { 17 //無靜態(tài)成員 18 });為了不讓抽象類實例化,我們直接在其構(gòu)造函數(shù)中拋出異常,為了不能直接調(diào)用抽象方法,我們也直接在其抽象方法中拋出異常,這樣我們就滿足了抽象類/方法的嚴(yán)格要求。
至此,JavaScript中面向?qū)ο蟮膶崿F(xiàn)就結(jié)束了,其類的實現(xiàn)也是一種模塊化,這樣代碼的可讀性就一步加強,具體在我們的日常工作中,很少會可以這樣封裝,也沒有必要,但在大型web應(yīng)用中,用模塊化、抽象化來重構(gòu)js代碼將顯得比較迫切,用面向?qū)ο笕ッ鎸π枨蟮亩鄻有?#xff0c;以最少的改動去滿足新的需求,何樂而不為,因為分享,所以快樂,在與大家交流中成長~~~
轉(zhuǎn)載于:https://www.cnblogs.com/freshfish/p/3402824.html
總結(jié)
以上是生活随笔為你收集整理的javascript 学习笔记之面向对象编程(二):继承多态的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Delphi程序的主题(Theme)设置
- 下一篇: 《大规模Web服务开发技术》