正如我們在 資料類型 章節中所知,JavaScript 中有八種資料類型。其中七種被稱為「基本型別」,因為它們的值只包含單一項目(無論是字串、數字或其他任何東西)。
相反地,物件用於儲存各種資料和更複雜實體的鍵值集合。在 JavaScript 中,物件幾乎滲透到語言的每個面向。因此,在深入探討其他任何內容之前,我們必須先了解它們。
可以使用大括號 {…}
來建立物件,並附上一個選用的 屬性 清單。屬性是一個「金鑰:值」配對,其中 金鑰
是字串(也稱為「屬性名稱」),而 值
可以是任何東西。
我們可以將物件想像成一個裝有簽名檔案的櫃子。每筆資料都以金鑰儲存在其檔案中。透過名稱可以輕鬆找到檔案,或新增/移除檔案。
可以使用兩種語法建立一個空物件(「空櫃子」)
let user = new Object(); // "object constructor" syntax
let user = {}; // "object literal" syntax
通常使用大括號 {...}
。該宣告稱為物件文字。
文字和屬性
我們可以立即將一些屬性放入 {...}
中,作為「鍵:值」對
let user = { // an object
name: "John", // by key "name" store value "John"
age: 30 // by key "age" store value 30
};
屬性在冒號 ":"
之前有一個鍵(也稱為「名稱」或「識別碼」),並且在冒號的右側有一個值。
在 user
物件中,有兩個屬性
- 第一個屬性的名稱為
"name"
,值為"John"
。 - 第二個屬性的名稱為
"age"
,值為30
。
產生的 user
物件可以想像成一個櫃子,上面貼有兩個標籤為「名稱」和「年齡」的簽名檔案。
我們可以隨時新增、移除和讀取其中的檔案。
可以使用點號表示法存取屬性值
// get property values of the object:
alert( user.name ); // John
alert( user.age ); // 30
值可以是任何類型。我們來新增一個布林值
user.isAdmin = true;
若要移除屬性,可以使用 delete
算子
delete user.age;
我們也可以使用多字元屬性名稱,但必須加上引號
let user = {
name: "John",
age: 30,
"likes birds": true // multiword property name must be quoted
};
清單中的最後一個屬性可以用逗號結尾
let user = {
name: "John",
age: 30,
}
這稱為「尾隨」或「懸掛」逗號。可以更輕鬆地新增/移除/移動屬性,因為所有行都變得相同。
方括號
對於多字元屬性,點號存取無法運作
// this would give a syntax error
user.likes birds = true
JavaScript 無法理解。它認為我們要處理 user.likes
,然後在遇到意外的 birds
時會產生語法錯誤。
點號需要鍵為有效的變數識別碼。這表示:不包含空格,不以數字開頭,不包含特殊字元(允許使用 $
和 _
)。
有一個替代的「方括號表示法」,可搭配任何字串運作
let user = {};
// set
user["likes birds"] = true;
// get
alert(user["likes birds"]); // true
// delete
delete user["likes birds"];
現在一切都很好。請注意,括號內的字串有加上適當的引號(任何類型的引號都可以)。
方括號也提供一種方法,可以取得屬性名稱作為任何表達式的結果,而不是文字字串,例如從變數取得,如下所示
let key = "likes birds";
// same as user["likes birds"] = true;
user[key] = true;
在此,變數 key
可以在執行階段計算或取決於使用者輸入。然後我們使用它來存取屬性。這讓我們有很大的彈性。
例如
let user = {
name: "John",
age: 30
};
let key = prompt("What do you want to know about the user?", "name");
// access by variable
alert( user[key] ); // John (if enter "name")
點號表示法無法以類似的方式使用
let user = {
name: "John",
age: 30
};
let key = "name";
alert( user.key ) // undefined
計算屬性
建立物件時,我們可以在物件文字中使用方括號。這稱為計算屬性。
例如
let fruit = prompt("Which fruit to buy?", "apple");
let bag = {
[fruit]: 5, // the name of the property is taken from the variable fruit
};
alert( bag.apple ); // 5 if fruit="apple"
計算屬性的意義很簡單:[fruit]
表示屬性名稱應該從 fruit
取得。
因此,如果訪客輸入 "apple"
,bag
將會變成 {apple: 5}
。
基本上,這與下列運作方式相同
let fruit = prompt("Which fruit to buy?", "apple");
let bag = {};
// take property name from the fruit variable
bag[fruit] = 5;
…但看起來比較漂亮。
我們可以在方括號中使用更複雜的表達式
let fruit = 'apple';
let bag = {
[fruit + 'Computers']: 5 // bag.appleComputers = 5
};
方括號比點號表示法強大許多。它們允許使用任何屬性名稱和變數。但它們的寫法也比較繁瑣。
因此,在大部分情況下,當屬性名稱已知且簡單時,會使用點號。如果我們需要更複雜的內容,則會改用方括號。
屬性值簡寫
在實際程式碼中,我們經常使用現有變數作為屬性名稱的值。
例如
function makeUser(name, age) {
return {
name: name,
age: age,
// ...other properties
};
}
let user = makeUser("John", 30);
alert(user.name); // John
在上面的範例中,屬性與變數具有相同的名稱。從變數建立屬性的使用案例非常常見,因此有一個特殊的屬性值簡寫可以讓它更簡短。
我們可以只寫 name
,取代 name:name
,如下所示
function makeUser(name, age) {
return {
name, // same as name: name
age, // same as age: age
// ...
};
}
我們可以在同一個物件中同時使用一般屬性和簡寫
let user = {
name, // same as name:name
age: 30
};
屬性名稱限制
正如我們所知,變數不能使用語言保留字作為名稱,例如「for」、「let」、「return」等。
但對於物件屬性,沒有這樣的限制
// these properties are all right
let obj = {
for: 1,
let: 2,
return: 3
};
alert( obj.for + obj.let + obj.return ); // 6
簡而言之,屬性名稱沒有限制。它們可以是任何字串或符號(一種特殊的識別符號類型,稍後會討論)。
其他類型會自動轉換為字串。
例如,數字 0
在用作屬性鍵時會變成字串 "0"
let obj = {
0: "test" // same as "0": "test"
};
// both alerts access the same property (the number 0 is converted to string "0")
alert( obj["0"] ); // test
alert( obj[0] ); // test (same property)
有一個名為 __proto__
的特殊屬性有一個小問題。我們無法將它設定為非物件值
let obj = {};
obj.__proto__ = 5; // assign a number
alert(obj.__proto__); // [object Object] - the value is an object, didn't work as intended
從程式碼中可以看到,指派給基本型別 5
會被忽略。
我們將在 後續章節 中討論 __proto__
的特殊性質,並建議 修復 此類行為的方法。
屬性存在測試,「in」運算子
與許多其他語言相比,JavaScript 中物件的一個顯著特點是,可以存取任何屬性。如果屬性不存在,不會產生錯誤!
讀取不存在的屬性只會傳回 undefined
。因此,我們可以輕鬆測試屬性是否存在
let user = {};
alert( user.noSuchProperty === undefined ); // true means "no such property"
還有一個用於此目的的特殊運算子 "in"
。
語法為
"key" in object
例如
let user = { name: "John", age: 30 };
alert( "age" in user ); // true, user.age exists
alert( "blabla" in user ); // false, user.blabla doesn't exist
請注意,in
的左側必須是屬性名稱。這通常是一個帶引號的字串。
如果我們省略引號,表示變數應包含要測試的實際名稱。例如
let user = { age: 30 };
let key = "age";
alert( key in user ); // true, property "age" exists
in
運算子為何存在?與 undefined
進行比較不就夠了嗎?
嗯,大多數時候與 undefined
進行比較都能正常運作。但有一個特殊情況會失敗,但 "in"
卻能正確運作。
這是當物件屬性存在,但儲存 undefined
時
let obj = {
test: undefined
};
alert( obj.test ); // it's undefined, so - no such property?
alert( "test" in obj ); // true, the property does exist!
在上述程式碼中,屬性 obj.test
在技術上存在。因此,in
運算子運作正確。
像這樣的狀況很少發生,因為不應明確指定 undefined
。我們大多使用 null
表示「未知」或「空」值。因此,in
運算子在程式碼中是一個罕見的訪客。
「for..in」迴圈
若要遍歷物件的所有鍵,有一個迴圈的特殊形式:for..in
。這與我們之前研究過的 for(;;)
結構完全不同。
語法
for (key in object) {
// executes the body for each key among object properties
}
例如,讓我們輸出 user
的所有屬性
let user = {
name: "John",
age: 30,
isAdmin: true
};
for (let key in user) {
// keys
alert( key ); // name, age, isAdmin
// values for the keys
alert( user[key] ); // John, 30, true
}
請注意,所有「for」結構都允許我們在迴圈內宣告迴圈變數,例如此處的 let key
。
此外,我們可以在這裡使用 key
以外的另一個變數名稱。例如,"for (let prop in obj)"
也廣泛使用。
像物件一樣排序
物件是有序的嗎?換句話說,如果我們遍歷一個物件,我們會以新增順序取得所有屬性嗎?我們可以依賴這個嗎?
簡短的答案是:「以特殊方式排序」:整數屬性已排序,其他屬性則按建立順序顯示。詳細資訊如下。
舉例來說,我們考慮一個包含電話代碼的物件
let codes = {
"49": "Germany",
"41": "Switzerland",
"44": "Great Britain",
// ..,
"1": "USA"
};
for (let code in codes) {
alert(code); // 1, 41, 44, 49
}
物件可用於向使用者建議選項清單。如果我們製作一個主要針對德語受眾的網站,那麼我們可能會希望 49
是第一個。
但是,如果我們執行程式碼,我們會看到完全不同的畫面
- 美國 (1) 排在第一個
- 然後是瑞士 (41) 等等。
電話區碼會以遞增排序,因為它們是整數。因此,我們會看到 1, 41, 44, 49
。
此處的「整數屬性」一詞表示一個字串,可以不變更地轉換為整數或從整數轉換回來。
因此,"49"
是整數屬性名稱,因為當它轉換為整數數字並返回時,它仍然相同。但是 "+49"
和 "1.2"
則不是
// Number(...) explicitly converts to a number
// Math.trunc is a built-in function that removes the decimal part
alert( String(Math.trunc(Number("49"))) ); // "49", same, integer property
alert( String(Math.trunc(Number("+49"))) ); // "49", not same "+49" ⇒ not integer property
alert( String(Math.trunc(Number("1.2"))) ); // "1", not same "1.2" ⇒ not integer property
…另一方面,如果鍵是非整數,則它們會按建立順序列出,例如
let user = {
name: "John",
surname: "Smith"
};
user.age = 25; // add one more
// non-integer properties are listed in the creation order
for (let prop in user) {
alert( prop ); // name, surname, age
}
因此,要修正電話區碼的問題,我們可以透過讓區碼成為非整數來「作弊」。在每個區碼之前加上加號 "+"
就足夠了。
像這樣
let codes = {
"+49": "Germany",
"+41": "Switzerland",
"+44": "Great Britain",
// ..,
"+1": "USA"
};
for (let code in codes) {
alert( +code ); // 49, 41, 44, 1
}
現在它可以按預期運作。
摘要
物件是具有多個特殊功能的關聯式陣列。
它們儲存屬性(鍵值對),其中
- 屬性鍵必須是字串或符號(通常是字串)。
- 值可以是任何類型。
要存取屬性,我們可以使用
- 點表示法:
obj.property
。 - 方括號表示法
obj["property"]
。方括號允許從變數中取得鍵,例如obj[varWithKey]
。
其他運算子
- 要刪除屬性:
delete obj.prop
。 - 要檢查具有給定鍵的屬性是否存在:
"key" in obj
。 - 要反覆運算物件:
for (let key in obj)
迴圈。
我們在本章節中所學習的內容稱為「純粹物件」,或僅稱為 Object
。
JavaScript 中還有許多其他類型的物件
Array
用於儲存已排序的資料集合,Date
用於儲存日期和時間的資訊,Error
用於儲存錯誤的資訊。- …等等。
它們有我們稍後會學習的特殊功能。有時候人們會說「陣列類型」或「日期類型」,但正式來說它們不是自己的類型,而是屬於單一的「物件」資料類型。而且它們會以各種方式擴充它。
JavaScript 中的物件非常強大。我們在此僅觸及了一個非常龐大的主題的表面。我們將在教學課程的後續部分與物件密切合作,並進一步了解它們。
留言
<code>
標籤,要插入多行,請將它們包在<pre>
標籤中,要插入超過 10 行,請使用沙盒 (plnkr、jsbin、codepen…)