03 三月, 2006 13:55
問題的來源,還是在自己不夠熟悉Javascript的prototype base inheritance。有一些想法還是自以為是地來自class base的觀念。結果讓instance共用了constructor的屬性,產生了非預期的結果...
最初是因為要做一個不間斷的跑馬燈,所以為跑馬燈設計了一個訊息物件:
function messages () {
this.msgQueue = new Array;
}
messages.prototype.addMsg = function (strMsg) {
this.msgQueue.push(strMsg);
}
(我有簡化過)
然後透過不同的方法來繼承(因為會有不同的實作):
function dynamicMsg () {
this.status = 0;
......
}
dynamicMsg.prototype = new messages;
//問題從這裡開始發生......
為了在頁面上產生兩個跑馬燈,所以用兩個變數:
var msg1 = new dynamicMsg;
msg1.addMsg("test1");
msg1.addMsg("test2");
var msg2 = new dynamicMsg;
msg2.addMsg("test3");
msg2.addMsg("test4");
結果問題就出現了,原本以為這樣msg1.msgQueue陣列裡面會有兩個元素"test1"跟"test2"而msg2.msgQueue會有兩個元素"test3"跟"test4"。沒想到,結果是msg1.msgQueue跟msg2.msgQueue陣列同樣都有四個元素,就是"test1"、"test2"、"test3"、"test4"!
這下子搞得我天下大亂,怎麼會發生這種事呢?我當然可以把msgQueue屬性跟addMsg方法放到dynamicMsg物件中來避開這個問題,但是我還是希望瞭解一下發生了什麼事情。
先在程式設計師論壇上問了一下,有人建議我參考過去的文章,用function物件的call方法來建構API。我試了一下,果然改一下就可以了,但是需要把原來用prototype的方式改成call的方式。
function dynamicMsg () {
messages.call(this); this.status = 0;
......
}
到底發生了甚麼問題呢?我後來做了一個測試:
function mother () {
this.fname = new Array;
}
mother.prototype.setfname = function (str) {
this.fname.push(str);
}
function son (title) {
this.title = title;
}
son.prototype = new mother;
var son1 = new son("workbee");
son1.setfname("test");
var son2 = new son("manager");
alert(son.prototype.fname);
alert(son1.fname);
alert(son2.fname);
結果,透過三次alert顯示出來的結果都是"test"!看起來,在透過new產生新的物件實體時,並不會改變前面利用prototype來assign給新物件實體的內容,所以son1.fname跟son2.fname同樣參考到了son.prototype.fname。真是殘念阿....
利用prototype來產生inheritance chain的方法是我在Core Javascript Guide裡面學到的,但是我沒有想到prototype base跟class base有甚麼不同,就把他拿來用了。
回頭看了一下ECMA-262,發現mother,son,son1,son2都是物件的identifier,並不是mother、son是宣告,而son1、son2是物件實體。而且son、mother這兩個函數物件也就是consturctor,在產生物件時會呼叫constructor,但是在constructor之外用prototype建立起來的參考不會改變。問題就在這裡發生了。
看起來,要熟悉Javascript,還需要把這些東西搞清楚才行。
推文( 0 )

/*
Try this...
*/
function mother () {
this.fname = new Array;
}
mother.prototype.setfname = function (str) {
this.fname.push(str);
}
function son (title) {
this.title = title;
m = new mother;
for(var i in m) {
this[i] = m[i];
}
}
//son.prototype = new mother;
/*
The 'son' is already a Function object.
When you write var son1 = new son(). It allocate a Function object like 'son', init the member by codes inside Function, then assign refrence of 'son.prototype' to 'son1'.
In your old way, it only invoke mother() once, so just allocate one array.
*/