本章探討 JavaScript 中三種主要的集合型別:Array、Map 和 Set,介紹如何有效地建立、修改與操作這些集合。
9.1 Arrays#
JavaScript 的陣列本質上是物件(objects),這與 C 等強型別語言中連續記憶體的陣列有根本差異。因為是物件,陣列可以存取方法,但在效能上也有一些特殊之處。
建立陣列#
有兩種基本方式建立陣列:
- 陣列字面值(Array literal):
[] - Array 建構函式:
new Array()
const ninjas = ["Kuma", "Hattori", "Yagyu"];
const samurai = new Array("Oda", "Tomoe");建議優先使用陣列字面值
[],因為語法更簡潔(2 個字元 vs 11 個字元),而且 JavaScript 的動態特性允許覆寫內建的Array建構函式,使用字面值可避免此風險。
重要特性:
- 每個陣列都有
length屬性,表示陣列大小 - 索引從
0開始,最後一個元素索引為array.length - 1 - 存取超出範圍的索引會回傳
undefined(不會拋出例外) - 對超出範圍的索引寫入值會自動擴展陣列,中間產生的空位為
undefined - 手動將
length設為較小的值會截斷陣列
在陣列兩端增刪元素#
push:在陣列末端新增元素pop:從陣列末端移除元素unshift:在陣列開頭新增元素shift:從陣列開頭移除元素
push和pop只影響陣列的最後一個元素,效能較好。shift和unshift會改變第一個元素,導致後續所有元素的索引都需要調整,因此效能較差。除非有特殊需求,建議優先使用push和pop。
在任意位置增刪元素#
使用 delete 運算子刪除陣列元素會留下一個 undefined 的「洞」,length 不會改變,這通常不是我們想要的結果。
正確做法是使用 splice 方法:
const ninjas = ["Yagyu", "Kuma", "Hattori", "Fuma"];
// 從索引 1 開始移除 1 個元素
var removedItems = ninjas.splice(1, 1);
// ninjas: ["Yagyu", "Hattori", "Fuma"]
// removedItems: ["Kuma"]
// 從索引 1 開始,移除 2 個元素,並插入 3 個新元素
removedItems = ninjas.splice(1, 2, "Mochizuki", "Yoshi", "Momochi");
// ninjas: ["Yagyu", "Mochizuki", "Yoshi", "Momochi"]
常見陣列操作#
迭代(Iterating):
- 傳統
for迴圈需要手動管理計數器變數,容易出錯 forEach方法更簡潔,接受一個 callback,為每個元素立即呼叫
const ninjas = ["Yagyu", "Kuma", "Hattori"];
ninjas.forEach((ninja) => {
assert(ninja !== null, ninja);
});
Figure 9.4: 使用 for 迴圈遍歷陣列的輸出結果
映射(Mapping):
map方法對每個元素呼叫 callback,用回傳值建立一個全新的陣列
const weapons = ninjas.map((ninja) => ninja.weapon);測試(Testing):
every:所有元素都滿足條件才回傳true(遇到false立即停止)some:只要有一個元素滿足條件就回傳true(遇到true立即停止)
搜尋(Finding):
find:回傳第一個滿足條件的元素,找不到則回傳undefinedfilter:回傳所有滿足條件的元素組成的新陣列indexOf/lastIndexOf:回傳特定項目的第一個 / 最後一個索引findIndex:類似find,但回傳的是索引而非元素本身
排序(Sorting):
sort方法接受一個比較用的 callback,回傳值決定排序順序:< 0:a排在b前面= 0:維持不變> 0:a排在b後面
array.sort((a, b) => a - b);聚合(Aggregating):
reduce方法將陣列歸納為單一值,接受一個累加器 callback 和初始值
const sum = numbers.reduce((aggregated, number) => aggregated + number, 0);重用內建陣列函式#
可以在自訂物件上重用 Array.prototype 的方法,透過 call 或 apply 明確設定方法的呼叫上下文:
const elems = {
length: 0,
add: function (elem) {
Array.prototype.push.call(this, elem);
},
find: function (callback) {
return Array.prototype.find.call(this, callback);
},
};這個技巧利用了 push 方法會自動遞增 length 屬性並以數字索引新增元素的特性,讓普通物件也能模擬陣列行為。
9.2 Maps#
Map 是一種將鍵(key)對應到值(value)的集合,也稱為 dictionary。
不要使用物件作為 Map#
使用普通物件作為 Map 有兩個嚴重問題:
- 原型屬性污染:所有物件都有原型,即使是空物件
{}也能存取到constructor等未明確定義的屬性 - 鍵只能是字串:非字串的鍵會被隱式轉換為字串。例如用 DOM 元素作鍵,不同的 DOM 元素可能被轉成相同的字串(如
"[object HTMLDivElement]"),導致互相覆蓋
由於原型繼承和字串限制,普通物件不適合作為 Map 使用。應改用 ES6 的內建
Map集合。

Figure 9.12: 物件不適合作為 map,因為它們會存取到原型鏈上的屬性

Figure 9.13: 物件的鍵會被轉為字串
建立與使用 Map#
使用 new Map() 建構函式建立 Map:
const ninjaIslandMap = new Map();
ninjaIslandMap.set(ninja1, { homeIsland: "Honshu" });
ninjaIslandMap.set(ninja2, { homeIsland: "Hokkaido" });
ninjaIslandMap.get(ninja1).homeIsland; // "Honshu"
Map 的主要方法與屬性:
set(key, value):建立鍵值對應get(key):取得鍵對應的值,不存在則回傳undefinedhas(key):檢查鍵是否存在delete(key):刪除指定鍵clear():清除所有鍵值對size:回傳目前的對應數量
鍵的相等性(Key Equality): Map 的鍵比較基於物件引用(reference equality),兩個內容相同但引用不同的物件會被視為不同的鍵。JavaScript 無法覆載相等運算子,這點需要特別注意。

Figure 9.15: Map 中的鍵相等性基於物件參照
迭代 Map#
Map 可以使用 for...of 迴圈迭代,保證按照插入順序遍歷:
for (let item of directory) {
// item[0] 是 key,item[1] 是 value
}也可以使用 keys() 和 values() 方法分別迭代鍵或值。
9.3 Sets#
Set 是不重複元素的集合,每個元素只能出現一次。ES6 之前需要用物件模擬,但存在與 Map 相同的原型污染和字串鍵限制。
建立與使用 Set#
const ninjas = new Set(["Kuma", "Hattori", "Yagyu", "Hattori"]);
// size 為 3,重複的 "Hattori" 被自動忽略
Set 的主要方法:
has(item):檢查元素是否存在add(item):新增元素(已存在則無效果)size:回傳元素數量- 可使用
for...of迴圈迭代,按插入順序遍歷

Figure 9.16: Set 中的元素按插入順序迭代
聯集(Union)#
建立包含兩個集合所有元素的新 Set,利用展開運算子合併陣列:
const ninjas = ["Kuma", "Hattori", "Yagyu"];
const samurai = ["Hattori", "Oda", "Tomoe"];
const warriors = new Set([...ninjas, ...samurai]);
// 包含 5 個元素,Hattori 只出現一次
交集(Intersection)#
建立只包含兩個集合共有元素的新 Set:
const ninjaSamurais = new Set(
[...ninjas].filter((ninja) => samurai.has(ninja))
);先用展開運算子將 Set 轉為陣列,再用 filter 方法篩選同時存在於另一個 Set 中的元素。
差集(Difference)#
建立只包含在 A 中但不在 B 中的元素的新 Set:
const pureNinjas = new Set([...ninjas].filter((ninja) => !samurai.has(ninja)));與交集的唯一差別是在 has 前加上 !,篩選不存在於另一個 Set 的元素。
9.4 本章重點#
- Array 是特殊的物件,具有
length屬性和Array.prototype作為原型 - 可用陣列字面值
[]或new Array()建立陣列 - 陣列提供豐富的內建方法:
push/pop、shift/unshift、splice、map、every/some、find/filter、sort、reduce - 可透過
call或apply在自訂物件上重用陣列方法 - 物件不適合作為 Map,因為有原型屬性污染和只支援字串鍵的限制,應使用內建的
Map - Map 是鍵值對集合,可使用
for...of迴圈迭代 - Set 是不重複元素的集合