EcmaScript 2015 (又稱ES6)通過一些新的關鍵字,使類成為了JS中一個新的一等公民。但是目前為止,這些關於類的新關鍵字僅僅是建立在舊的原型系統上的
語法糖,所以它們並沒有帶來任何的新特性。不過,它使代碼的可讀性變得更高,並且為今后版本里更多面向對象的新特性打下了基礎。
在介紹 class 繼承以前,先來回憶一下沒有 class 之前類是怎么被創建和繼承的:
1、定義 function father 構造函數,再通過 prototype 定義 father 類原型方法。
2、定義 function child 構造函數,構造函數內部通過 father.call( this, arguments ) 的方式獲得父類的實例屬性。
3、通過 child.prototype = new father() 的方式獲取 father 的原型方法。
4、最后還必須 child.prototype.constructor = child 的方式進行子類構造函數的還原。
再從 child 上繼續往下派生呢? 項目代碼的可讀性越來越差,代碼結構也越來越不清晰。當然本文並未打算討論 javascript 的不是。在能基本滿足生產要求的情況下,暫時也不會去深入探討 function class prototype __proto__ constructor 等等內容的錯綜復雜的關系,實際上我們需要的是清晰的代碼層次,滿足繼承關系即可,至於里邊的實現,可能不會顯得那么重要,一般情況下,我們並不會需要去了解的如此的深入。
廢話不多說,開始本文的主要內容。
class 作為類的使用,可參見我的另外一篇博文:js面向對象設計之class類。下面主要介紹 class 的繼承。
先來看一個簡單的例子:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y);
this.color = color;
}
toString() {
return this.color + ' ' + super.toString();
}
}
let ins = new ColorPoint(1,2,'red');
console.log( ins.toString() ); /* red [object Object] */
class 繼承一個比較關鍵的引用 super 在上述例子中出現了兩次。分別是在構造函數中和類方法中。需要注意的是 super 只能在子類中使用。super 在構造函數中的作用和在子類方法中的作用並不同。
在構造函數中,super 是一個函數,它調用父類的構造函數,並隱式的返回一個 this。如果在 super(x,y) 前面執行 this.color 程序會報錯。原因前一句已經說了,super 會隱式的返回一個 this,而子類的構造函數的初始化全部都是基於這個 this 的。這是 class 中的一種機制,關於構造函數的這部分內容無須記得很牢,因為在開發過程中,如果子類的構造函數不使用 super(...),或在錯誤的地方調用它,子類都會報錯,所以無論如何,你都會記得很牢(否則程序無法開發下去)。
而在子類的方法中,super 作為一個引用指向父類的方法。這個特性在 function 中也可以實現,雖然需要寫一些不那么好閱讀和維護的代碼,比如你可以先保存 father.toString 的引用(當然需要注意 this 的指向問題,可以采用 bind 或 call 等方式),然后再寫 child.toString = function(){...},在這里面調用父類的方法。也許有更優雅的寫法。但是能有 class 的寫法更加優雅簡潔易讀和易維護嗎,顯然不能,那我們就必須往前看了。在成為資深前端之前,可以忘掉 function。
在使用 super 調用父類的方法時,super 內部的 this 指向子類。逆向思維來想想,這個 this 也只能指向子類的實例,調用者就是它。除非用 bind call apply 這些東西。
關於 super 博主將來會深挖更多的東西,現在暫且就了解到這個地步。
ins 同時是 Point 和 ColorPoint 的實例,這個讀者自行驗證。這也符合 ES5 的規范。
上面的例子中,ins 的屬性 x 是在原型上,還是在實例上?這里從 super 的理解上着手立馬就有答案,x 屬性是實例的,不是原型的。
這個例子講解了 class 類的公有屬性和公有方法的相關繼承問題。公有屬性全部都在實例上,而公有方法全部都在原型上。這個就不詳細展開了。
下面是 class 的靜態方法相關處理方式。
class A { static geta(){ console.log( 'A' ); } }
class B extends A { static getb(){ console.log( 'B' ); } }
B.geta(); /* A */
B.getb(); /* B */
下面來看看 class 中的私有屬性在繼承中的相關反應。先看一個例子:
var A = ( function(){
var _name;
class A {
constructor() {}
getName(){ console.log( _name ); }
setName( name ){ _name = name; return this; }
}
return A;
} )();
class B extends A { }
let ins = new B();
ins.setName('nDos').getName(); // nDos
上例中 B 繼承於 A,B 的實例 ins 可以通過 A 的公有方法使用 A 的私有變量 _name。
小tips:class 中可以讓指定的類無法被實例化,通過判斷 new.target === theClass(定義的類名)的真假拋出錯誤,使得 theClass 不能被實例化而只能被繼承,這是一個很有用的 tips。不用擔心繼承的問題,子類實例化的時候,父類的構造函數中 new.target 指向的是子類。參見下例:
class A {
constructor() {
new.target === A || console.log('A 為抽象類,不能被實例化')
}
}
class B extends A { }
let ins = new B(); /* A 為抽象類,不能被實例化 */
class 的繼承還可以從原生構造函數中繼承,我的另外一篇博文有提過。關於這方面的內容需要單獨行文探討,此處暫時不討論,僅僅提一下。
class Mixin 混合繼承。這個是為了解決多重繼承的問題而來。什么是多重繼承,多重繼承是編程語言中的概念,多重繼承指的是一個類可以繼承另外一個類,而另外一個類又可以繼承別的類,比如A類繼承B類,而A類又可以繼承C類,這就是多重繼承。這里將要將的就是這個。但是 Mixin 更加的靈活,使得代碼結構也更加清晰優雅。
var A = ( function(){
var _name;
class A {
constructor() {}
getName(){ console.log( _name ); }
setName( name ){ _name = name; return this; }
}
return A;
} )();
function Mixin ( BaseClass ) {
return class extends BaseClass
{
mixin(){ console.log('這是混合繼承的類的方法'); }
}
}
class C extends Mixin(A)
{
getC(){ console.log('c'); }
}
let ins = new C();
ins.mixin(); // 這是混合繼承的類的方法
ins.setName('nDos').getName(); // nDos
ins.getC(); // c