昨天扔了一堆JavaScript類'繼承'的代碼,這些代碼其實並不是所有的都能正常的執行。不是我不原意寫出都能好好執行的繼承類代碼,而是這些方法本身就各自有自己的優缺點。下面我分別說它們的原理和使用時注意事項。
構造繼承法的原理:
構造繼承法關鍵代碼是function ArrayList01()中的:
this.base = CollectionBase;
this.base();
這裡的base不是C#衍生類別中的base那個概念,完全就是一個任意的JavaScript變數名。調用this.base();其實就是執行的CollectionBase();,不過不是new CollectionBase();哦!沒有new基類那麼怎麼得到CollectionBase中的方法和屬性呢?這裡使用了this範圍的一個hack,'欺騙'了指令碼引擎。當我們從類ArrayList01的建構函式中調用this.base();時,在基類CollectionBase中的this就是ArrayList01的一個執行個體,於是執行CollectionBase的建構函式,就動態把基類的成員和方法attach到ArrayList01執行個體中了。構造法的問題也就是從這裡產生了,注意哦。
構造繼承法的缺陷:
第一個問題,關鍵是出在上面那兩段代碼上,因為始終沒有new基類呀。這樣帶來的問題就是不能把基類CollectionBase中的原型屬性和方法attach到ArrayList01的執行個體中,啊,這樣還算是什麼繼承啊?! 所以在上篇文章中我為了不改動CollectionBase類,而單獨為其實現了一個Add()方法(只是為了統一範例程式碼而已)。解決這個缺陷的方法其實也很簡單,就是要求基類不能使用prototype來匯入屬性和方法,而要把所有的屬性和方法都寫到建構函式中去,分別是:this.Attribute = ...; 和 this.Method = function() {...};這種形式。
第二個問題,是this.base = CollectionBase;和this.base();必須寫在衍生類別建構函式的最開頭(不是一定要是第一行和第二行,而是它們的前面不能有this.xxx這種定義為ArrayList01匯入的任何屬性或方法),因為調用this.base();時會向this(ArrayList01的一個執行個體)中注入基類的屬性和方法,如果基類中有和this中已匯入的屬性和方法重名的,就自動覆蓋掉子類中的方法。
第三個問題,是子類也不能使用prototype來匯入屬性和方法,這和問題二中的重名覆蓋道理一樣,由於prototype匯入的屬性和方法在子類一new的時候就產生了,所以也存在和基類重名而被覆蓋的潛在錯誤威脅。解決辦法,和基類編寫規則一樣不要使用prototype,並且將子類的屬性和方法定義代碼放在匯入繼承的代碼(this.base = CollectionBase;this.base();)之後。
構造繼承法的樣本:
<script language="JavaScript">
document.write('構造繼承法:<br>');
var arrayList11 = new ArrayList01();
arrayList11.Add('a');
arrayList11.Add('b');
arrayList11.foo();
var arrayList12 = new ArrayList01();
arrayList12.Add('a');
arrayList12.Add('b');
arrayList12.Add('c');
arrayList12.foo();
</script>
樣本運行結果為:
構造繼承法:
[class ArrayList01]: 2: a,b
[class ArrayList01]: 3: a,b,c
小結一下:JavaScript的構造繼承法其實看起來還是比較直觀的,因為子類建構函式中有對基類建構函式的調用,似乎在文法上還比較容易被接受。可是由於沒有new基類,帶來了不能獲得prototype匯入的屬性和方法的缺陷。解決這個缺陷的方法雖然不難,可是由此而給基類編寫限制一個淩駕於JavaScript文法規範的規則,可操作性不是很好。子類也不能利用prototype特性來匯入屬性和方法,會與基類之間存在潛在的重名覆蓋問題。所以對於複雜的基類,不推薦這種繼承方法,因為類複雜了,使用prototype可以規範類代碼,使類的定義看起來比較的舒服。
應用情境:小規模類之間的繼承,基類和子類的屬性方法在5-8個。還有就是以建構函式中賦值方式匯入類的屬性和方法,而不用prototype匯入的類編寫習慣的時候。
to be continued ...