2011年7月29日 星期五

JavaScript 的 prototype

以下將我目前理解的內容寫下來, 有錯還請指正。

概念

翻了數篇說明文後, 發覺任何嘗試用 class-based OOPL 來解釋 prototype 的文章, 剛開始看起來滿合理的, 最後都會愈解釋愈複雜, 並且產生矛盾。這或許和大部份人根深蒂固認為 「 使用 (class-based) OOP == 易於維護的程式碼」一樣, 是難以破除的迷思, 總會想用 class-based OO 來解讀其它語言。個人認為這和學校老師和坊間一堆入門書有很大的關係。直接理解 prototype 的設計理念和它對應的用法, 會比較順。

若有耐性的話, 看完《Details of the object model》, 就會清楚 prototype 為何。不過要留意文中關於 class-based OOPL 的描述有些不正確, 像 Ruby、Python 這類 dynamic typing 的語言, 定義完 class 後仍能修改 class 的屬性。沒耐性的話, 可以看較簡短的說明: 《JavaScript 的 prototype(上)》《以 C 語言實做 Javascript 的 prototype 特性》

用我自己的說法, JavaScript 的 OO 特性為:

  • 所有物件都是一樣的, 只有 "instance", 沒有 class 和 instance 之分 (反之, 就得花費不少篇幅說明 class 是 "Class" 的 instance)。
  • 物件之間的關係透過 prototype chain 串起來: 每個物件都有它的 prototype, 自己找不到屬性時, 就往 prototype 找。
  • 使用 closure 封裝 scope。待理解得比較深之後, 再另寫篇文章說明。

prototype chain 的效果

從下面這段程式可看出它的效果:

function X() {}
function Y() {}
X.prototype = new Y;
x = new X;
console.log(x.a); // undefined
Y.prototype.a = "yy";
console.log(x.a); // "yy"; x.a -> X.prototype.a -> Y.prototype.a
X.prototype.a = "xx";
console.log(x.a); // "xx"; x.a -> X.prototype.a
x.a = "aa";
console.log(x.a); // "aa"; x.a

接著這段程式在 new 完物件後, 換掉 prototype, 藉此突顯 prototype chain 是順著物件的 __proto__ 找, 而不是找 X.prototype 指到的物件。

function X() {}
x = new X();
console.log(x instanceof X); // true
console.log(x.a); // undefined
X.prototype.a = 3;
console.log(x.a); // 3
X_original_prototype = X.prototype;
function Y() {}
X.prototype = new Y; // 換掉 prototype
console.log(X.prototype.a); // undefined
console.log(x instanceof X); // false
console.log(x.a); // 3
X_original_prototype.a = 5;
console.log(x.a); // 5
X.prototype = X_original_prototype;
console.log(x instanceof X); // true

Foo() 和 new Foo() 的差異

function Foo() {
    this.a = 3;
    return 9;
}
x = Foo();
console.log(x); // 9
x = new Foo();
console.log(x); // Foo { a=3 }

總結

  • 對 function Foo 來說, x = Foo() 和 x = new Foo() 的行為截然不同。前者是呼函式取得回傳值; 後者不會取得回傳值, 而是產生一個新的物件, 並將 x.__proto__ 指向 Foo.prototype。
  • __proto__" 是 JavaScript interpreter 內部記錄 prototype 的物件, 不是正式的 JavaScript 定義
  • 執行 function Foo() {} 後, 會產生物件 Foo:
    • typeof(Foo) === "function"
    • 產生 Foo.constructor, 其值是一個函式, 即 "function Foo() {}"
    • 產生 Foo.prototype, 可被換為其它物件
  • foo = new Foo() 會產生物件 foo:
  • 一般的 function call (少了 new) 只會得到回傳值, 不會串起 prototype chain
  • JavaScript 沒有區分 method 和 field, 都視作 property
  • foo.x 會先查自己是否有這個 x 這個屬性。沒有的話, 再往 foo.__proto__ 找; 還是沒有的話, 再往 foo.__proto__.__proto__ 找, 直到沒有 __proto__ 為止 (即 Object.prototype)

習慣 prototype 的思維後, 我覺得這個設計還挺漂亮的, 透過一致且簡單的規則, 可以彈性地加減共通屬性。

2 則留言:

  1. http://jashkenas.github.com/coffee-script/

    CoffeeScript把繼承的行為包裝的漂亮很多,閒來無事可以看一下
    :)

    回覆刪除
  2. 謝啦, 待 JS 語法摸熟一些後來看看

    回覆刪除

在 Fedora 下裝 id-utils

Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...