輕鬆學習JavaScript十三:JavaScript基於物件導向之繼承(包含物件導向繼承機制)
一面相對象繼承機制
今天算是什麼都沒幹,盡在瞭解物件導向三大特性之一的繼承了,過去的學習的C++和C#都是正統的物件導向語
言,學習的時候也沒有怎麼深入瞭解過,只是簡單的學習最基礎的繼承。下午在看繼承機制的時候,看到一個很經典
的繼承機制執行個體。這個執行個體使用UML很好的解釋了繼承機制。
說明繼承機制最簡單的方式是,利用一個經典的例子就是幾何形狀。實際上,幾何形狀只有兩種,即橢圓形(是圓
形的)和多邊形(具有一定數量的邊)。圓是橢圓的一種,它只有一個焦點。三角形、矩形和五邊形都是多邊形的一種,
具有不同數量的邊。正方形是矩形的一種,所有的邊等長。這就構成了一種完美的繼承關係,很好的解釋了物件導向
的繼承機制。
在這個例子中,形狀是橢圓形和多邊形的基類(通常我們也可以叫它父類,所有類都由它繼承而來)。橢圓具有一
個屬性(foci),說明橢圓具有的焦點的個數。圓形繼承了橢圓形,因此圓形是橢圓形的子類,橢圓形是圓形的超類。同
樣,三角形、矩形和五邊形都是多邊形的子類,多邊形是它們的超類。最後,正方形繼承了矩形。
最好用圖來解釋這種繼承關係,這是 UML(整合模組化語言)的用武之地。UML的主要用途之一是,可視化地表示像
繼承這樣的複雜物件關係。下面的圖示是解釋形狀和它的子類之間關係的UML圖示:
在UML中,每個方框表示一個類,由類名說明。三角形 、矩形和五邊形頂部的線段彙集在一起,指向形狀,說明
這些類都由形狀繼承而來。同樣,從正方形指向矩形的箭頭說明了它們之間的繼承關係。
二ECMAScript繼承機制的實現
要用ECMAScript實現繼承機制,您可以從要繼承的基類入手。所有開發人員定義的類都可作為基類。出於安全原
因,本地類和宿主類不能作為基類,這樣可以防止公用訪問編譯過的瀏覽器級的代碼,因為這些代碼可以被用於惡意
攻擊。
選定基類後,就可以建立它的子類了。是否使用基類完全由你決定。有時,你可能想建立一個不能直接使用的基
類,它只是用於給子類提供通用的函數。在這種情況下,基類被看作抽象類別。儘管ECMAScript並沒有像其他語言那樣
嚴格地定義抽象類別,但有時它的確會建立一些不允許使用的類。通常,我們稱這種類為抽象類別。
建立的子類將繼承超類的所有屬性和方法,包括建構函式及方法的實現。記住,所有屬性和方法都是公用的,因
此子類可直接存取這些方法。子類還可添加超類中沒有的新屬性和方法,也可以覆蓋超類的屬性和方法。由於JS並不
是正統的物件導向語言,一些名詞也需要做出改變。
三ECMAScript繼承的方式
ECMAScript語言中將被繼承的類(基類)稱為超類型,子類(或衍生類別)稱為子類型。和其他功能一樣,ECMAScript
實現繼承的方式不止一種。這是因為JavaScript中的繼承機制並不是明確規定的,而是通過模仿實現的。這意味著所
有的繼承細節並非完全由解釋程式處理。作為開發人員,你有權決定最適用的繼承方式。下面為您介紹幾種具體的繼承
方式。
(1)原型鏈方式
繼承這種形式在ECMAScript中原本是用於原型鏈的。上一篇博文已經介紹了建立對象的原型方式。原型鏈擴充了
這種方式,以一種有趣的方式實現繼承機制。prototype 對象是個模板,要執行個體化的對象都以這個模板為基礎。總而
言之,prototype 對象的任何屬性和方法都被傳遞給那個類的所有執行個體。原型鏈利用這種功能來實現繼承機制。我們
來看一個例子:
function A() {//超類型A中必須沒有參數 this.color = "red"; this.showColor = function () { return this.color; };};function B() {//子類型B this.name = "John"; this.showName = function () { return this.name; };};B.prototype = new A();//子類型B繼承了超類型A,通過原型,形成鏈條var a = new A();var b = new B();document.write(a.showColor());//輸出:bluedocument.write(b.showColor());//輸出:reddocument.write(b.showName());//輸出:John在原型鏈中,instanceof運算子的運行方式也很獨特。對B的所有執行個體,instanceof為A和B都返回true。
ECMAScript的弱類型世界中,這是極其有用的工具,不過使用對象冒充時不能使用它。例如:
var b = new B();document.write(b instanceof A);//輸出:truedocument.write(b instanceof B);//輸出:true
使用原型鏈方式實現了繼承,但是這種方式無法共用和子類型給超類型傳遞參數。我們可以借用建構函式方式(也
就是對像冒充)的方式來解決這兩個問題。
(2)對象冒充方式
對象冒充方式的其原理如下:建構函式使用this關鍵字給所有屬性和方法賦值(即採用對象聲明的建構函式方式)。
因為建構函式只是一個函數,所以可使A建構函式成為B的方法,然後調用它。B就會收到A的建構函式中定義的屬性
和方法。例如,用下面的方式改寫上面的例子建立對象A和B:
1call()方法
function A(Color) {//建立超類型A this.color = Color; this.showColor = function () { return this.color; };};function B(Color,Name) {//建立子類型B A.call(this, Color);//對象冒充,給超類型傳參 this.name = Name;//新添加的屬性 this.showName = };var a = new A("blue");var b = new B("red", "John");document.write(a.showColor());//輸出:bluedocument.write(b.showColor());//輸出:reddocument.write(b.showName());//輸出:John2apply()方法
和上面call()方法唯一的區別就是在子類型B中的代碼:
A.call(this,arguments);//對象冒充,給超類型傳參
當然,只有超類型中的參數順序與子類型中的參數順序完全一致時才可以傳遞參數對象。如果不是,就必須建立
一個單獨的數組,按照正確的順序放置參數。
使用對象冒充方式雖然解決了共用和傳參的問題,但是沒有原型,複用就更不可能了,所以我們組合上述的兩種
方式,即原型鏈方式和對象冒充的方式實現JS的繼承。
(3)混合方式
這種繼承方式使用建構函式定義類,並非使用任何原型。對象冒充的主要問題是必須使用建構函式方式,這不是
最好的選擇。不過如果使用原型鏈,就無法使用帶參數的建構函式了。開發人員如何選擇呢?答案很簡單,兩者都用。
由於這種混合方式使用了原型鏈,所以instanceof運算子仍能正確運行。
在上一篇博文,建立對象的最好方式是用建構函式定義屬性,用原型定義方法。這種方式同樣適用於繼承機制,
用對象冒充繼承建構函式的屬性,用原型鏈繼承prototype對象的方法。用這兩種方式重寫前面的例子,代碼如下:
function A(Color) { this.color = Color;};A.prototype.showColor = function () { return this.color;};function B(Color, Name) { A.call(this, Color);//對象冒充 this.name = Name;};B.prototype = new A();//使用原型鏈繼承B.prototype.showName = function () { return this.name;};var a = new A("blue");var b = new B("red", "John");document.write(a.showColor());//輸出:bluedocument.write(b.showColor());//輸出:reddocument.write(b.showName());//輸出:John繼承的方式和建立對象的方式有一定的聯絡,推薦使用的繼承方式還時原型鏈和對象冒充的混合方式。使用這種
混合方式可以避免一些不必要的問題。
看這篇博文的時候,必須看一下前面的建立對象的方式:輕鬆學習JavaScript十二:JavaScript基於物件導向之創
建對象(一)和輕鬆學習JavaScript十二:JavaScript基於物件導向之建立對象(二)。那麼理解起來應該沒有那麼難了,
JS物件導向的一些概念時需要我們回過頭來再理解的。