在房子裡面可以放你想放的任意事物——如果你有足夠的美學造詣,你甚至可以弄一個房中房試試——當然,為了方便管理,我們會給房子裡存放的所有事物都會取上一個不重複的名字,比如醫藥房間裡的各種藥品名稱。在ECMAScript中,你可以在對象中存放任意你想放的資料,同樣,我們需要給存放的資料取一個名字——也就是對象的屬性名稱,再存放各種資料。再看看ECMA-262中對象的定義:無序屬性的集合,其屬性可以包含單一資料型別值、對象或者函數。
進入對象,我開始有些激動了,說實話,讓我想起做這系列學習筆記的最初原因,就是因為該書對對象的深刻論述,讓我對JavaScript的認知從用戶端驗證小工具轉變成一門強大的物件導向指令碼語言,但我現在也有點犯難了,因為關於對象,有太多太多的東西需要去細化,一時也不知該從哪個點切入,比如要想深入理解對象,範圍、執行環境、閉包這些概念是肯定離不開的,但如果連對象的概念都沒說就開始執行環境和閉包,又感覺像是空中樓閣。不過又一想,也就釋然了,這畢竟只是自己的個人學習筆記,又不是什麼教科書,我大可以使用自己喜歡的方式來做自己的筆記(事實上,在前面的篇章中,我就有意識的重複那些我認為有意思的地方,這就是我喜歡的一種方式),當然,我還是會盡量以一種易於理解的方式來做這些筆記。
物件類型
和5種單一資料型別(Undefined、Null、Boolean、Number、String)相對應,對象(Object)也是一種資料類型,只是這種資料類型比較特別,它不但可以像單一資料型別一樣存取通常的資料,而且可以將動作行為作為一種特殊的資料加以存取。
1、對象執行個體
每種資料類型都有相應的值,比如Undefined類型只有一個值undefined,而數字5是Number類型的一個值。對於物件類型,我們把值稱為對象執行個體,那麼物件類型都可以有哪些(值)執行個體呢?任意一個對象都是物件類型的值(執行個體),比如簡單類型封裝對象(Boolean、Number、String)就是物件類型的值(執行個體)。
2、對象字面量
既然任意一個對象都是物件類型的執行個體,那麼對象執行個體怎麼表示呢?或者說我們在交流過程中怎麼書寫出對象執行個體呢?單一資料型別的值很好表示,比如用符號“5”表示數字5,符號“true”表示Boolean值true,這些被稱為字面量,那麼,有沒有對象字面量呢?答案是肯定的,對象字面量就是通過一對大括弧({})來表示的。比如: 複製代碼 代碼如下:{
name:'linjisong',
getName:function(){
return this.name;
}
}
這裡最外層的一對大括弧({})就表示這是一個對象字面量。另外,還有數組字面量的概念,在ECMAScript中,數組Array是一個繼承了Object的對象執行個體,通過這個對象執行個體可以建立數群組類型的執行個體,數群組類型的執行個體也可以直接通過數組字面量來表示,方法如下: 複製代碼 代碼如下:[{
name:'linjisong',
age:29
},{
name:'oulinhai',
age:23
}]
這裡一對中括弧([])用於表示數組,這是一個包含了兩個對象的數組。通過對象字面量和數組字面量,形成了難以想象的強大表現力,事實上,流行的JSON資料格式就是基於此。
3、建立對象執行個體
熟悉一般物件導向的朋友都知道,要建立一個類的執行個體,首先要定義這個類,然後用new關鍵字來建立這個類的執行個體(別和我說還可以使用反射,我的Java可學的不好……)。但是在ECMAScript中,根本沒有類的概念,那麼,對象執行個體要怎麼建立呢?
在ECMAScript中儘管沒有類,但是也有某種程度上類似的概念,承擔這個角色的就是函數,可以通過new操作符和函數來建立對象執行個體——每一個對象執行個體都有一個用於建立這個執行個體的函數。最基本的函數就是Object(),它是用來建立最一般對象的函數,其它的諸如Number()函數,可以用來建立Number對象的執行個體,Boolean()函數,可以用來建立Boolean對象的執行個體: 複製代碼 代碼如下:var obj = new Object();//Object()函數,建立最一般的對象執行個體
var num = new Number(1);//Number()函數,建立Number對象的執行個體
var boo = new Boolean(true);//Boolean()函數,建立Boolean對象的執行個體
console.info(typeof num);//object
console.info(typeof Number(1));//number
console.info(typeof boo);//object
console.info(typeof Boolean(true));//boolean
(1)可以看到,要建立一個對象執行個體,首先需要有一個函數(稱為建構函式),這個函數使用new調用時就是建立對象執行個體,不使用new時只是通常意義上的函數調用(如果這個函數在內部返回執行個體了,函數調用也可以建立對象)。
(2)所謂的內建對象實際上也就是內建了一些建立對象執行個體的函數而已,不同的函數建立不同的內建對象。
(3)關於要不要使用new操作符,我的建議是使用,如果不使用new操作符,有些情況下結果會出乎你的意料之外,像上例中的第5、7行,實際上並沒有建立對象,而只是普通的函數調用,這個調用的作用就是轉換資料類型。
(4)使用new建立對象執行個體時,如果調用建構函式不需要傳入參數,也可以省略後面的函數叫用作業符(()),當然,這種特性也不是什麼值得宣揚的事情。
(5)如果需要建立自訂對象的執行個體,那麼首先也需要定義一個建構函式,然後使用new操作符調用建立執行個體。這裡需要注意,如果忘了new的話,可能會汙染全域環境: 複製代碼 代碼如下:function Person(){//首先定義一個用於建立對象執行個體的(構造)函數
this.name = 'linjisong';
this.age = 29;
}
var person = new Person();//調用(構造)函數建立對象執行個體
console.info(person.age);//29
try{
console.info(age);//為了示範忘記使用new的情況,這裡先輸出全域的age,由於未定義,拋出異常
}catch(e){
console.info(e);//ReferenceError
}
var person2 = Person();//忘記使用new的情況下,只是普通的函數調用,由於函數沒有返回,這裡person2就是undefined了
console.info(person2);//undefined
console.info(age);//29,沒有使用new,內部的this指向了全域範圍,因為可以在全域訪問age了
要避免這種問題,可以修改一下建構函式: 複製代碼 代碼如下:function Person(){
if(this instanceof Person)
{
this.name = 'linjisong';
this.age = 29;
}else{
return new Person();
}
}
var person2 = Person();
console.info(person2.age);//29,可以訪問person2的age了
console.info(age);//全域環境中沒有age的定義了,拋出異常
這個建構函式首先判斷this值是否為Person類型,如果不是,就在內部使用new調用,以確保返回的值一定是Person類型執行個體。這種方式使得重構建構函式成為了可能,也許Boolean()、Number()、String()在實現上就是使用了這種方式來區分是建構函式還是轉換函式。如果你在調用Object()時省略new的話,結果也能返回對象,估計也是在後台做了類似處理,同樣的情況還有本文後部分要講的函數類型建構函式Function()。
(5)可能有人會問,既然有對象字面量,何必要用這麼複雜的方式來建立對象執行個體呢,直接寫對象字面量不就完了?用對象字面量建立對象執行個體,根本沒有使用什麼函數,看來,上面的“每一個對象執行個體都有一個用於建立這個執行個體的函數”的說法並不正確。
首先第一個問題,的確,可以使用對象字面量來建立函數,而且也非常簡潔,這甚至也是我首先推薦的一種建立方式,但是用這種方式建立對象執行個體,只能建立單例的執行個體,對於需要建立多個相同類型的對象執行個體來說並不適用,然後第二個問題,用對象字面量建立對象,實際上並不是沒有相應的建構函式,只是建構函式為Object(),使用對象字面量,後台可能不會去調用new Object(),但建立出的對象仍然有指向這個函數的屬性,這可以從下面代碼輸出中得到驗證: 複製代碼 代碼如下:var person = {};
console.info(person.constructor===Object);//true
這裡的constructor是每個執行個體對象都有的一個屬性,用於儲存建立這個對象執行個體的函數,這就是下面要講的。
4、對象屬性和方法
每一種資料類型都有各自的共性,比如Number類型值都有可以和另外一個Number類型值相加的特性,同樣,物件類型的執行個體也有一些相同的特性,這些特性就體現在它們都包含下面的屬性和方法(方法實際上也是一種屬性,只是屬性的實值型別是函數的話,我們也稱之為方法):
類別 |
屬性/方法 |
說明 |
屬性 |
constructor |
指向用於建立當前對象的函數 |
方法 |
hasOwnProperty(propertyName) |
檢查給定的屬性是否在當前對象執行個體中 |
propertyIsEnumerable(propertyName) |
檢查給定的屬性是否能夠是使用for-in語句來枚舉 |
isPrototype(object) |
檢查傳入的對象是否是另一個對象的原型 |
toLocalString() |
返回對象的字串表示,該字串與執行環境的地區相對應 |
toString() |
返回對象的字串表示 |
valueOf() |
返回對象的字串、數值或布爾值表示,通常與toString()方法傳回值相同 |
註:在《JavaScript進階程式設計(第3版)》第35頁中的Constructor將首字母大寫了,應該是一個印刷錯誤。
屬性和方法的訪問有兩種方式:
(1)使用點號(.):如person.name。
(2)使用方括弧([]):如person[name],使用這種方式,方括弧內部可以是一個變數或者運算式,這使得可以訪問名稱包含特殊符號的屬性和方法。
通過結合for-in和這裡的hasOwnProperty (propertyName),我們就可以遍曆對象執行個體自身的屬性而不包括從原型鏈繼承而來的屬性了: 複製代碼 代碼如下:for(var propertyName in object){
if(object.hasOwnPorperty(propertyName)){
//迴圈處理
}
}