以下將我目前理解的內容寫下來, 有錯還請指正。
概念
翻了數篇說明文後, 發覺任何嘗試用 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:
- typeof(foo) === "object"
- 將 x.__proto__ 指到目前的Foo.prototype
- 可以換掉 Foo.prototype, 不過在 new 完後才換 prototype 會造成困惑; 多數情況下, 可以換掉 constructor
- 一般的 function call (少了 new) 只會得到回傳值, 不會串起 prototype chain
- JavaScript 沒有區分 method 和 field, 都視作 property
- foo.x 會先查自己是否有這個 x 這個屬性。沒有的話, 再往 foo.__proto__ 找; 還是沒有的話, 再往 foo.__proto__.__proto__ 找, 直到沒有 __proto__ 為止 (即 Object.prototype)
習慣 prototype 的思維後, 我覺得這個設計還挺漂亮的, 透過一致且簡單的規則, 可以彈性地加減共通屬性。
http://jashkenas.github.com/coffee-script/
回覆刪除CoffeeScript把繼承的行為包裝的漂亮很多,閒來無事可以看一下
:)
謝啦, 待 JS 語法摸熟一些後來看看
回覆刪除