以下將我目前理解的內容寫下來, 有錯還請指正。
概念
翻了數篇說明文後, 發覺任何嘗試用 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 語法摸熟一些後來看看
回覆刪除