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