2012年1月16日 星期一

Javascript用prototype進行繼承會發生的奇怪現象



這問題真的很奇怪,而且很難理解。
測試網址如下(code很簡短,可直接看原始碼):
http://labs.medialand.com.tw/jason/html/inheritance/test1.html

狀況是,
1. 建立一個Test的類別,在建構子時宣告並初始化一個陣列testValue。
2. 在Test類別建立一個function addValue,用來將傳入的值push到testValue裡。
3. 在Test類別建立一個function destroy,用來將testValue清空。
4. 建立一個AA類別繼承Test類別。
5. 實體化一個AA類別,變數名稱a1,呼叫a1.addValue(123)將123這個值push到a1.testValue裡。
6. 呼叫a1.destroy()來清空a1.testValue。
7. 目前看起來都正常,怪的來了。
8. 實體化另外一個AA類別,變數名稱a2。
9. 直接取得a2.testValue,居然不是空值,而是123!!!
照道理說,testValue裡有123這個值,不管怎樣也是屬於a1這個instance的,不應該會出現在新new出來的instance裡。更何況在a1的時候已經執行過一次destroy來清掉testValue了。
無法理解。
這種情況目前測試,只發生在testValue非一般常數型態的變數上,數字跟字串不會發生這種情況,在array或object上就會發生這種異常的狀況。

解決方法應該有很多,目前我覺得我自己最喜歡的方式,就是在AA類別的建構子裡再一次強制呼叫父類別Test的建構子,就像AS3裡的super();一樣。
再測過一次後這個問題就會被解決。
修正過後的測試網址如下(差別只在於AA的建構子裡多了一行):
http://labs.medialand.com.tw/jason/html/inheritance/test2.html


2 則留言:

  1. 我最近剛好也有遇到這個問題,後來我理解了,也許可以看看我的觀點:)

    你的addValue應該是這樣實做的

    Test.addValue = function(a){
    this.testValue.push(a);
    }

    當你的執行a1.addValue(123)時,是執行this.testValue.push(a);

    由於a1實體裡並沒有testValue屬性所以他會往__proto__裡找,找到testValue後並回傳(這時的返回值是a1.__proto__.testValue)。

    然後你對這個值做push自然會對a1.__proto__.testValue新增123,此時prototype已經被更改。

    然而你執行delete a1.testValue,卻沒有影響是因為 a1.testValue其實是a1.__proto__.testValue的返回值

    你的解決方法在AA類別裡強制呼叫Test的建構子,使得a1實體擁有一個testValue各別屬性,也就解決了prototype chain的問題了。

    回覆刪除
  2. 原本的問題是 destroy的 this.testValue = [],可改為 this.testValue.length = 0,不能使用 "new" 讓它指向新的陣列,可修改原本指向物件的成員,不然這裡會變成給 a1一個獨有的屬性 testValue、且為空陣列,沒有修改到 prototype裡的testValue,仍是123。(可以在 a1.destroy()前後加入console.log(a1.hasOwnProperty("testValue"));)觀察)。

    回覆刪除