javascirpt是和C#有著相當不同的語言,C#是面向類的靜態編程語言(這裡暫不考慮C# 4.0添加的動態特性),而javascript是面向對象的函數式編程語言。
雖然C#和javascript差別很大, 但是我們還有可能利用已經掌握的技能來加速對新技能的掌握。
一、javascirpt面向對象特性。
C#是面象類的。它通過“2步法”來獲得一個可以操做的對象。首先通過class關鍵字來定義一個類,然後通過new關鍵字獲得一個對象。這包含了以下幾層意思。
1,首先我們需要定義一個對象的結構,這就是類
2,我們通過這個結構,賦予狀態來獲得一個對象(或者叫實例對象)
3,在使用new關鍵字的時候,系統會調用一個構造函數,來給對象賦予一個初始狀態。
總結: C#需要先決定對象的結構,然後再決定對象的狀態, 並且在決定了對象的狀態後,不允許再改變對象的結構。所謂的“2步法”就是先結構,後狀態。且有了狀態後,結構不允許再發生變動。
javascript可以采用“1步法”,也就是說決定結構和狀態的操作在同一個步驟內完成。
例如 cat.color="while", 這個操作在給對象添加了一個叫color(顏色)的結構的同時,賦予了"white"(白色)這個屬性。而上面這個操作是在程序的任何地方使用的。
由於使用“1步法",類的存在就沒有了意義。只要我們有了一個對象,我們就可以隨時隨地給對象同時添加結構和狀態,或者叫屬性和賦值。(如果硬要套上類的概念的話,那麼javasciprt裡面的類是不停地在變化的)
在C#中new關鍵字是產生一個對象唯一手段,在javascript中new關鍵字只是產生一個對象的2個方式中的一種。
第一種產生一個對象的方式是使用“{}"操作符。
baby={};
singer={name:"張學友“,sex: "男“}
第1個對象沒有什麼結構,第2個對象有名有性。以後我們還可以添加其他屬性。
到這裡你也許會問,有了那麼方便的產生對象的“{}"操作符,為什麼還需要 new關鍵字呢?
利用new關鍵字,可以調用一個函數(即構造函數,或者叫constructor)來完成構建對象的動作,從而獲得“2步法“的效果。
比如
function Person(name,sex) {
this.name=name;
this.sex =sex;
}
singer=new Person("張學友",”男")
這裡雖然看上去和c#的寫法很象,似乎采用了“2步法“,Person函數定義了結構,new語句賦予了狀態。但是這個“2步法“和C#的“2步法“有所區別,因為它的"2步“不是很嚴格的“2步”。
如果我們把構造裡面的語句注釋掉,換成:
function Person(name,sex) {
// this.name=name;
// this.sex =sex;
}
singer=new Persion()
singer.name="張學有"
singer.sex="男“
最後得到的singer對象和前例得到的singer對象完全相同。
我們可以自由的把定義結構的第一步,移出構造函數,仍然使用“1步法“來獲得等價的效果。
由於javascript可以隨時通過“1步法”同時添加結構和狀態, 因此使用“{}"還是使用“new"來產生新對象,可以按照實際需求來自由選擇了(很free吧)。
二,javascript的繼承。
由於javascript的對象構建采用了“1步法“,同時添加結構和狀態, 所以javascript的繼承也是“同步”的,同時繼承結構和狀態。
在C#中,是通過類來繼承,並且只繼承結構,不繼承狀態,這個繼承通過父類來實現。而在javascript中需要同時繼承結構和狀態,因此是通過對象來繼承了。
因此C#是面向類的語言,javascript是面向對象的語言(同時繼承結構和狀態)。
一個比較標准的說法是javascript通過prototype來實現繼承。其實所謂prototype就是父對象(和C#中的父類相對應)。子對象繼承父對象的所有結構和狀態(前提是自己對應的結構和狀態不存在)
這裡有一個學習上的難點,就是一個對象並不直接通過prototype屬性連接到它的父對象,而是通過constructor屬性的prototype對象,間接的連接到它的父對象。
代碼表示如下:
function Monkey() {
}
function Person() {
}
singer=new Person();
Person.prototype=new Monkey()
Person.prototype.constructor= Person
// assert_true singer.constructor == Person 也就是說singer對象有一個constructor屬性指向了Person,而Person再通過prototype屬性指向 new Money()對象
喜歡刨根問底的人一定會問,為什麼一個子對象不能直接有一個prototype屬性來直接指向父對象呢?
其實這是可以的,例如在Firefox中,每一個對象都有一個__proto__屬性指向它的父對象。(但是這個屬性是不建議用戶使用的)
那為什麼其他浏覽器不這麼做呢?而要通過constructor(構造函數)來間接的連接到父對象呢。其實這是有充分理由的,是一個精心的設計。
如果每個對象可以直接連接到父親對象,那麼會出現一個非常混亂的情況,如果我們隨時在父對象通過“1步法”同時添加屬性和狀態,隨時會被子對象繼承。如果出錯了,那麼我們需要檢查整個程序來找到造成麻煩的那句話。這顯然不是個好的設計。
那麼該怎麼辦呢?
通過上面的例子,我們知道通過構造函數,我們可以獲得2步法的效果,因此我們把父對象連接到子對象的構造函數的prototype屬性上,並且定一個“君子協定”,凡是需要添加屬性,都放到構造函數中,這樣我們只要檢查構造函數,就可以知道子對象繼承了哪些結構(注意,狀態的繼承不受限制,因為構造函數是2步法中的第一步,決定結構)。
由於要給prototype賦值一定要借助於構造函數,因此就“不動聲色的,誘導性的“,讓你很“自然地“使用“2步法“來完成繼承工作,讓代碼組織性更好。但是由於是“君子協定”,你如果在構造函數外添加結構,也完全做得到,不過也許會在某些情況下造成一些大小麻煩。
有朋友也許會問,本來都是對象很好理解,扯出來函數,反而迷糊了。其實函數就是對象。如果對這個事實感覺很難理解。我們可以試試采用下面的說法。
世上本來沒有函數,都是對象。不過有一種對象比較特殊,它有一個非常特殊的屬性“()”,使用這個屬性,需要省略“."這個操作符,例如是“Person()"而不是“Person.()",這個屬性不象 singer.name屬性直接輸出一個狀態,比如“張學友“,而是能夠通過它內部隱藏的一段代碼,完成一個計算,並且在“()”中可以添加一些參數,從而對它內部代碼的運行施加影響。由於它是一個對象,所以其他對象能夠出現的地方,它也能出現。最後我們把這種對象稱為函數。而它也讓函數式編程成為了可能。
至此,你有沒有發覺javascript的面向對象是那麼的優雅,靈活和簡潔呢?那個初看起來很難懂的prototype的設計,其實蘊含了設計者的良苦用心。
而javascript的函數式編程更將這種美發揮到了極致,簡直是驚為天人。(這個另文再說)。
另外通過學習javascript,既可以對C#的靜態特性有更好的理解。也有助於對c# 4.0添加的動態特性的進一步學習(雖然我對c#的動態特性並不看好,主要原因是同時包含靜態和動態特性反而讓程序員難以取捨。這也是Java對動態特性一直比較抗拒的原因)。
by助人為樂