1) Javascript 繼承機(jī)制的不足和改進(jìn)
問題(1)由于javascript是采取prototype機(jī)制的“偽繼承”,prototype必須顯示引用“基類”對象,所以注定了javascript只能實現(xiàn)“弱繼承”,或者叫做“對象繼承” 注意這里的次序關(guān)系需要很明確,prototype的賦值必須在對象構(gòu)造之外。 例如:
function classA() { classA.prototype.methodA = function() {...} } function classB() { classB.prototype.methodA = function(){...} } classB.prototype = new classA(); //注意這里聲明B繼承自A出現(xiàn)在function classB()函數(shù)體之外,并且先于 //classB對象的構(gòu)造被執(zhí)行。
由于是這樣一種機(jī)制,所以有兩點(diǎn)需要注意: 第一,在類的構(gòu)造函數(shù)中不應(yīng)當(dāng)執(zhí)行任何持久化操作,如全局的或者dom對象的構(gòu)造,因為如果你這么做了,當(dāng)使用這個類作繼承的時候,即使你只實例化一個對象,這個類的構(gòu)造函數(shù)也可能被執(zhí)行超過一次!(相反地,在真正面向?qū)ο蟮恼Z言,如C++和Java中,當(dāng)我們采用Singleton模式的時候,常常被允許在構(gòu)造函數(shù)中執(zhí)行持久化操作,如果習(xí)慣了這種模式,在javascript中很容易出錯?。?/span> 例如:
function uniqueForm(url) { this.id = ‘myform‘; this.action = url uniqueForm.prototype.Form_Submit = function(){...} this.form = document.creatElement(‘form‘); //問題出在這里,在構(gòu)造函數(shù)中進(jìn)行了持久化操作 ...... } function uniqueValidateForm(url) { ...... ...... } uniqueValidateForm.prototype = new uniqueForm();
var myForm = new uniqueValidateForm(‘test.aspx‘); //這個語句被執(zhí)行的時候其實已經(jīng)構(gòu)造的是第二個form元素了, //第一個元素在繼承語句uniqueValidateForm.prototype = new uniqueForm(); //中已經(jīng)被構(gòu)造出來了。
一個避免這個問題的方法是采用工廠: function formFactory() { formFactory.getCurrentForm = function() { if(formFactory.form == null) { formFactory.form = document.createElement(‘form‘); } return formFactory; } }
把上面例子中兩個類的document.createElement(‘form‘);改成formFactory.getCurrentForm()即可。
問題(2)類屬性的繼承:問題的根源同樣在于javascript的對象繼承機(jī)制。通過prototype實現(xiàn)的繼承很容易獲得基類的對象屬性和對象方法,但是卻根本不可能獲得基類的類方法。而真正的面向?qū)ο罄^承,應(yīng)當(dāng)是同時繼承類和對象方法才行。一種基本上可以解決這個問題的方案是使用反射遍歷基類的方法加到子類方法中去。例如:
function classA() { classA.prototype.methodA = function() {...} //這是一個對象方法 classA.methodA = function(){...} //這是一個類方法 }
function classB() { ...... } classB.prototype = new classA(); for (each in classA) { classB[each] = function(){classA[each]}; //將classA的類方法也繼承下來 }
var obj = new classB(); obj.methodA(); //調(diào)用繼承自A的對象方法 classB.methodA(); //調(diào)用繼承自A的類方法
問題(3)帶參數(shù)的基類構(gòu)造:正如上面提到的,由于prototype和對象繼承的限制,在構(gòu)造派生類對象之前必須先實例化基類對象,這給我們實現(xiàn)帶參繼承帶來了很大的麻煩。一種解決方法是等到需要構(gòu)造對象的時候才去處理繼承 例如:
classB.prototype = new classA(1, 2); //在一次構(gòu)造之前才實現(xiàn)繼承 var objB = new classB(1, 2); classB.prototype = new classA(-1, -2); //另一次構(gòu)造,參數(shù)不同所以要重寫繼承 var objC = new classB(-1, -2);
當(dāng)然,像上面這樣的做法給人的感覺很不好,都有點(diǎn)不像是繼承了。
還有另外一種做法是令B的構(gòu)造函數(shù)不依賴于A的構(gòu)造函數(shù),也就是說在B中重寫所有A中實現(xiàn)的構(gòu)造函數(shù)邏輯,而僅僅繼承B的屬性域和方法域,這樣在聲明繼承的時候只要提供給A的構(gòu)造函數(shù)任意一組合法的參數(shù)就行了。當(dāng)然這種方法也很麻煩。
事實上要解決上面這個問題,必須考慮在B中保留對A域的訪問并且將構(gòu)造函數(shù)邏輯抽象出來。 例如:
function classA(x, y) { classA.prototype.constructor = function(x, y) { ...... } ...... if (x != null || y!=null) this.constructor(x, y); } function classB(x, y) { classB.prototype.constructor = function(x, y) { this.base.constructor(x, y); //先調(diào)用基類的構(gòu)造函數(shù) ...... //再執(zhí)行自己的構(gòu)造函數(shù) } ...... if (x != null || y!=null) this.constructor(x, y); } classB.prototype = new classA(); classB.prototype.base = new classA();//用無參繼承
var objB = new classB(1, 2); var objC = new classB(-1, -2);
2)事件驅(qū)動機(jī)制和回調(diào)函數(shù)實現(xiàn)
javascript的靈活性使得其實現(xiàn)事件驅(qū)動和回調(diào)是很方便的,唯一的困擾就在于this指針。一般來說,在面向?qū)ο笾形覀兿M麑ο蠓椒ǖ?/span>this指針僅僅指代對象本身,而不應(yīng)當(dāng)有其他含義。在一般情況下,this指針在javascript的對象方法中也能很好的工作,但是很可惜在面對回調(diào)和事件驅(qū)動的時候就不適用了。根本原因是因為javascript的this指針是隨著調(diào)用者而改變的。在一般的對象方法中,調(diào)用者自然是對象本身,然而在回調(diào)函數(shù)和事件函數(shù)中,調(diào)用者可能是外部對象或者事件的真正傳播者。 例如:
function classA() { this.button = document.createElement(‘input‘); this.button.type = ‘button‘; ...... ...... this.button.onclick = classA.prototype.Button_Click; this.text = ‘請點(diǎn)擊‘; this.count = 0; ...... ...... ...... classA.prototype.Button_Click = function() //這樣寫得不到正確的結(jié)果 { alert(‘點(diǎn)擊次數(shù):" + ++this.count); //因為此時的this已經(jīng)代表調(diào)用者 this.button.text = ‘請再次點(diǎn)擊‘; //button,而不是classA對象了 } }
在復(fù)雜的事件和回調(diào)中,可能還會包含成員函數(shù)之間的相互調(diào)用和事件的關(guān)聯(lián),我們當(dāng)然不能將大量的精力花費(fèi)在判斷this指針?biāo)鶎偕?,一種比較巧妙的解決方法是引入一種固定的事件注冊模式,如上面的代碼寫成:
function classA() { this.button = document.createElement(‘input‘); this.button.type = ‘button‘; ...... ...... this.button.eventHandler = this; this.button.onclick = function(){this.eventHandler.Button_Click(this, event);}; //注意這個函數(shù)內(nèi)的this實際上在執(zhí)行的時候代表button this.text = ‘請點(diǎn)擊‘; this.count = 0; ...... ...... ...... classA.prototype.Button_Click = function(sender, event) { alert(‘點(diǎn)擊次數(shù):" + ++this.count); //正確了 this.button.text = ‘請再次點(diǎn)擊‘; //或者sender.text更為簡單 } }
3)抽象類(abstract Classes)
抽象類是面向?qū)ο笤O(shè)計中的一種重要元素,要發(fā)揮javascript面向?qū)ο蟮亩鄳B(tài)力量,必須要尋找一種實現(xiàn)抽象類的機(jī)制。 Javascript的靈活性使得我們可以用一種簡單的方式實現(xiàn)它 例如: function abstractA(Implemented) { abscractA.prototype.A = function(){...} ...... ...... ...... if (Implemented != true) { throw new Error(‘抽象類abstractA不能構(gòu)造實例!‘); } } function classA() { ...... }classA.prototype = new abstractA(true);
4)接口:接口也是一種非常重要的元素,然而實現(xiàn)它比較復(fù)雜 下面是一個例子: function InterfaceA(objA) { if (objA.methodA == objA.methodA) throw new Error(‘InterfaceA的方法MethodA在對象中未實現(xiàn)!"); if (objA.methodA == objA.methodB) throw new Error(‘InterfaceA的方法MethodB在對象中未實現(xiàn)!"); ...... ...... } function classA() { ...... }classA.prototype = new InterfaceA(new classA());
5)高級反射技術(shù)——元數(shù)據(jù)管理和多態(tài)回調(diào)
我們利用javascript類生成了一個對象之后,如何能夠在外部快速地獲得這個對象的句柄,反射技術(shù)為我們提供了一個思路,甚至我們還能在運(yùn)行時環(huán)境中動態(tài)地獲得一個類家族的所有實例而不需要事先知道它們的對象名。另外,這種技術(shù)的一個重要用途是實現(xiàn)局部對象的持久化。 由于這是一個比較復(fù)雜的應(yīng)用,只有在一些復(fù)雜的任務(wù)中才能體會到它的便利,這里只能舉一個極其簡單的例子:
function classA(id) { this.id = id; //這個ID對于元數(shù)據(jù)的建立是重要的 classA.instances[id] = this; classA.instances.All.push(this); ...... ...... } classA.instances = new Array(); classA.instances.All = new Array();
var objA = new classA(‘myObjA‘);
在外部用classA.instances[‘myObjA‘]可以直接訪問objA而無需知道對象名objA,另外還可以通過對classA.instances.All遍歷來訪問所有classA的實例。
|