物件properties在繼承時出問題???

 Fri, 03 Mar 2006 13:55:02 +0800

問題的來源,還是在自己不夠熟悉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,還需要把這些東西搞清楚才行。