JavaScript對象與繼承 JavaScript是我在C語言之後接觸的第二門編程語言,大一暑假的時候在圖書館找了一本中國人寫的 "JavaScript" 程式設計來看。那個時候在編程方面幾乎還是小白,再加上那本書根本沒有提JavaScript的編程機制,又有一些誤導性的話,一直以來對JavaSc ...
JavaScript對象與繼承
JavaScript是我在C語言之後接觸的第二門編程語言,大一暑假的時候在圖書館找了一本中國人寫的JavaScript程式設計來看。那個時候在編程方面幾乎還是小白,再加上那本書根本沒有提JavaScript的編程機制,又有一些誤導性的話,一直以來對JavaScript有很深的誤解,認為JavaScript只是一門在瀏覽器上運行的面向對象語言,值此文來寫下JavaScript當中很具有迷惑性和容易誤解的地方。當然限於作者水平有限,也沒有什麼開發經驗,所以難免有疏漏之處,還望批評指正。
JavaScript的對象
對象是什麼
JavaScript代碼當中隨處可見new關鍵字,很容易讓人產生誤解,認為JavaScript是Java一樣是基於類繼承的語言。但是事實並非如此,JavaScript當中並沒有類,那JavaScript的對象不是類那又是什麼呢?某種意義上說,JavaScript的對象就是Python當中的字典(哈希表),其實也就是類似這樣的鍵值對:
me={
"fisrtName" : "seek",
"lastName" : "truth" ,
"getName" : function(){
return this.firstName+this.lastName; //this相當於指向這個對象的指針
}
}
這是一個比較有誤解性的地方,初次看到時候覺得有點無法理解,但仔細用一用還是覺得合理,我們既可以像Python一樣用[]運算符來獲取元素,也可以用.操作符來獲取元素:
me.firstName // => seek
me["lastName"] //=> truth
me.getName() // => seektruth
new運算符
既然JavaScript當中是沒有類的,那麼new運算符又是在乾什麼呢?這是JavaScript設計的最讓人誤解的地方之一。JavaScript是一門函數式編程語言,JavaScript當中函數是一等公民,JavaScript當中函數也是對象,函數對象在被創建的時候會被添加調用屬性,比較坑的是JavaScript函數有兩種調用方式,一種是加了new關鍵字的調用,一種是沒有new關鍵字的調用,前者會返回一個對象,後者會返回return語句當中的內容。考慮下麵的一段函數:
function Obj(name){
this.name=name;
return name;
}
如果我們用new運算符來調用:
obj = new Obj("seektruth") //obj會是一個對象:{"name": "seektruth"}
如果我們直接調用:
obj = Obj("seektruth") //obj會是一個字元串:"seektruth"
確實設計的挺坑的,我們在調用的時候需要分清楚是否需要使用new,一般來說需要用new關鍵字來調用的函數會採用大寫開頭。
還有更坑的是如果返回的返回值是一個對象:
function Obj(name){
this.name=name;
return {};
}
這樣無論我們是否用new運算符來調用都會返回return語句里的值:
new Obj("seektruth") //=> {}
Obj("seektruth") //=> {}
設計的是什麼鬼......
對象繼承
原型
前面已經說到過JavaScript當中是沒有類的,那JavaScript又是怎麼來實現繼承的呢?答案是通過原型鏈。在JavaScript當中,每個對象都會有一個原型,在創建對象的時候,如果不加說明的話,對象繼承的原型是Object.prototype,函數對象會繼承Function.prototype(Function.prototype繼承Object.prototype):
Object.prototype // => {}
Function.prototype // => [Function]
我們可以通過對象的__proto__熟悉來查看對象的原型:
a={}
a.__proto__ // => {}
JavaScript通過指定對象的原型來實現繼承,指定對象的原型主要有三種方式,一是在構造函數當中指明原型,二是直接修改對象的__proto__屬性,三是利用Object.create函數,下麵我們依次來看一看
在構造函數當中指定原型
我們可以在構造函數當中指定對象的原型:
me={
"firstName" : "seek",
"lastName" : "truth" ,
"getName" : function(){
return this.firstName+this.lastName; //this相當於指向這個對象的指針
}
}
function Obj(name){
this.firstName = name;
this.__proto__ = me; //指定原型為me對象
}
指定了原型之後,我們新建了對象之後就可以訪問原型的屬性:
obj = new Obj("foo"); // => { firstName: 'foo' }
obj.firstName // => foo
obj.lastName // => truth
obj.getName() // => "footruth"
當訪問一個對象的時候,首先會嘗試在改對象當中尋找該屬性,如果沒有就回到原型當中尋找,直到Object.prototype。如果我們在新的對象當中重寫了原型當中的屬性(方法),那麼實際使用的時候我們新寫的屬性(方法)會覆蓋掉原型當中的定義,這有點像基於類的語言的函數重載。
註意如果原型me對象的lastname屬性有改變,因為obj對象是在原型當中尋找屬性,那麼這個obj對象的lastname屬性也會改變:
me.lastName = "me"
obj.lastName // => "me"
obj.getName() // => "foome"
直接改變對象的原型
我們也可以直接指定(改變)對象的原型:
obj2 = {}
obj2.__proto__ = me
obj2.firstName // => seek
obj2.lastName // => "me"
obj2.getName() // => "seekme"
使用Object.create函數
儘管說前兩種方法可以解決問題,但是這兩種寫法並不優雅,因為JavaScript並不是基於類的語言,第一寫法很容易給人以誤解,JavaScript語言精粹的作者Crockford認為new就不應該出現在JavaScript語言當中,而推薦使用Object.create函數來基於原型來創建對象。Object.create函數的用法很簡單:
obj3 = Object.create(me) // 以me為原型創建新的對象
obj3.firstName // => seek
obj3.lastName // => "me"
obj3.getName() // => "seekme"
obj3 = Object.create(me) 與obj2 = {};obj2.proto = me是等價的,但是前一種寫法更優雅也更易於理解。
總結
JavaScript作為一門基於原型的,函數式的編程語言在設計上有很多優雅與強大之處,但同時又有很多糟粕和坑,正式如此,JavaScript也是被誤解最多語言。學習了JavaScript的對象繼承機制,感覺自己的水平還是大有長進的。