自定義JavaScript類

Doc:http://docs.google.com/Doc?docid=0AZUdXGtQa0xqZGRocmo3MzZfMjEyaG5tNGdncXQ&hl=en

 

繼續裝機。看到裝機能賺錢,但咱沒特別的廠家支持,不過也沒關係,這樣能給用戶提供的電腦品種反而更多。OK,那咱也打算去申請個小工作間,開始給用戶裝機。我們這提供多種主流的硬件配置,比如提供了Acer、Dell,這樣用戶只要說給我裝個Acer的,ok,那就給用戶一個Acer的機器。但我們這裏不生產Acer的配件(當然Acer也不生產所有配件... anyway)我們會到其他地方(可能是Acer生產線)去拿配件來給用戶組裝。同時用戶看到我們這代理nVidia,他們也可以直接向我們要nVidia的顯卡(我們幫他們拿唄,賺點路費)。

到這裏我們把這個例子和js裏的類、對象對應起來看看。
我們的工作間 <-> js裏定義的類        // 只是個名字
工作間的功能 <-> 類的prototype     // 這裏有我們想給用戶提供什麼功能和東西
裝Acer的機器 <-> protytope裏的getAcer方法        // 具體方法了
來塊nVidia顯卡(因爲我們有渠道拿到顯卡)       <-> getNVidia方法    // 這個是prototype鏈提供的

以上廢話結束,直接進入代碼。
一般的說JavaScript來定義類的方法如下:

function MyClass () {}
MyClass.prototype.foo = function() {}
MyClass.prototype.bar = 'Hello world';
...

var myClass = new MyClass();

這樣會讓剛接觸js的人有點疑問,這js裏的函數怎麼又能做這種事呢,怎麼類和函數有啥關係?我不想把自己的關於“js裏並沒有類”的想法強加給別人(當然不一定對,但對指導目前),你能接受並且真正的理解是最好。或者這樣寫的話可能更符合我們舉的例子:

var MyClass = new Function();    // 定義了我們的類名
MyClass.prototype.foo = function() {}    // 定義了一個提供的方法

var myClass = new MyClass();    // 生成實例
myClass.foo();    // 執行方法

以上是大衆化的定義js類的方法,當然大家都是這麼寫的,但自己要明白js裏的類到底是如何工作的:prototype鏈。
我們先畫個草圖來表示一下類、prototype、實例的聯繫:
function

這個圖並不完整,有些東西沒有標在上面,比如prototype裏的constructor,{foo: ..., bar: ...}是一個對象,它也應該有個__proto__指向Object的prototype等等。爲了清晰說明prototype鏈的問題,我這裏不會把這些東西都畫出來,但你應該自己去考慮這些存在的東西(現在不會考慮沒問題,看完這篇後應該知道了)

OK,言歸正傳。
我們通過 function MyClass(), MyClass = new Function()等方法來定義這個函數是,系統會給他開闢一個prototype空間,默認裏面有個會有一些方法(如constructor,以後再說),這時候我們可以通過MyClass.prototype直接訪問它,並且增加、修改裏面的內容。
var myClass = new MyClass(); 時,系統生成了一個對象叫myClass,他有一個屬性__proto__,這個__proto__指向MyClass.prototype。
myClass.foo(); 這是js會看myClass這個對象裏面有沒有foo方法,有則執行,無則沿prototype鏈(通過__proto__)向上找,直到找到找到這個方法或者到達最頂層,沒該方法,出錯。
以這個例子爲基礎,再講一點:myClass這個對象裏面現在“有”foo方法和bar屬性(這裏的“有”其實是他能夠在prototype鏈上找到的,並不表示自己有,就想我們的裝機店,我們有各種配件,那是因爲我們能找到供應商,而不是自己生產)。
很多人會並不在意這一點,比如想“修改”myClass裏的bar的值:
myClass.bar = "changed";
看過上一篇“JavaScript對象”,我想首次指向這句的時候大家應該知道他做了什麼。myClass.bar並沒有把自己__proto__裏的bar的值修改掉(可以想想修改了會有什麼後果),而是給自己新增一個屬性bar,並賦值爲"changed"。
myClass

我們上面的MyClass看上去是一個空函數,貌似只提供了一個prototype給自己的實例去調用,下面的這種寫法可能比較實際一點。我們舉經典的例子,Shape。先看下如何定義一個Point。

function Point(x, y) {
    this.x = x;
    this.y = y;
}

var p = new Point(0, 0);

功能一目瞭然,定義了Point類,可以用兩個參數來初始化一個對象,這個對象會有x、y兩個屬性(這裏是真的有)。爲了表示的清晰一點,還是看圖:
Point

注意這裏Point.prototype裏面並沒有什麼自定義的屬性和方法了(我們沒定義嘛)
但是當運行 var p = new Point(); 時,js回去調用自己的constructor方法,constructor有時指向Point這個函數的... 關係有點怪,但這種結構也爲我們做一些js的底層框架庫提供了很好的設計基礎(一個對象可以通過constructor來獲知自己是誰的實例)。
在p的constructor方法(Point函數)裏面,我們通過this.x, this.y給p設置了兩個屬性,還是那句話,當我們嘗試對一個對象的屬性賦值時,他會給自己添加一個這樣的屬性(已有的話當然直接修改即可)。

這樣就完成了我們如何去定義一個js的類,sigh... 爲了表述的清楚,我還是使用類、實例這些OOP的概念,但還是強調一句,js裏面對象的工作方式是以prototype鏈爲基礎的,而不像c++、java裏面每個對象都會有自己的內存空間來存放自己的屬性。特別對於繼承來說,每個實例要存放自己類的屬性,還要存放基類的屬性,每個對象都是,不管這些屬性是否是隻讀的。我們將在下一篇“JavaScript類的繼承”裏看到js裏面是如何實現“繼承”的:prototype鏈。x你丫沒完沒了了,怎麼啥都是prototype鏈...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章