「Map」物件
本文說明「Map」物件。
我們將循序漸進地介紹,從基本操作到實際應用中有用的實例。
YouTube Video
「Map」物件
「Map」是一個儲存鍵值對的集合。它類似於物件,但不同之處在於任何類型,例如物件、函式或原始類型,都可以作為鍵,而且插入的順序會被保留。
「Map」的基本
首先,讓我們看看如何建立「Map」以及進行基本操作。
以下的程式碼是一個簡單的範例,用來建立一個空的 map,新增鍵並取得對應的值。
1// Create an empty Map and add key-value pairs
2const m = new Map();
3m.set('a', 1);
4m.set('b', 2);
5
6console.log(m.get('a')); // 1
7console.log(m.size); // 2
- 在此範例中,你可以使用「set」新增元素、用「get」取得值,並用「size」檢查元素數量。
- 「Map」會保留插入順序,因此很適合需要順序的應用。
「set」、「get」、「has」和「delete」的行為
以下是典型的讀取、寫入、存在檢查與刪除的操作範例。
藉由下方範例程式碼,可以檢查每個方法的回傳值及效果。
1// Demonstrate set, get, has, and delete
2const m2 = new Map();
3m2.set('x', 10);
4console.log(m2.has('x')); // true
5console.log(m2.get('y')); // undefined
6
7m2.delete('x');
8console.log(m2.has('x')); // false
9
10// set returns the map itself, allowing method chaining
11m2.set('a', 1).set('b', 2);
12console.log(m2); // Map { 'a' => 1, 'b' => 2 }
- 如果鍵不存在,「get」會回傳「undefined」。
has用來檢查某個鍵是否存在,delete則用來移除某個鍵。 - 另外,由於「set」會回傳自身的Map,所以可以進行方法鏈式呼叫。
任何型別皆可作為鍵(將物件作為鍵)
Map 的主要優點之一是可以直接使用物件作為鍵。
下列範例說明如何使用物件作為鍵來關聯Map中的值。
1// Use objects as keys in a Map
2const keyObj = { id: 1 };
3const keyFunc = () => {};
4const objMap = new Map();
5
6// Another object with the same content but a different reference
7const anotherKeyObj = { id: 1 };
8
9objMap.set(keyObj, 'objectValue');
10objMap.set(keyFunc, 'functionValue');
11objMap.set(anotherKeyObj, 'anotherValue');
12
13console.log(objMap.get(keyObj)); // 'objectValue'
14console.log(objMap.get(keyFunc)); // 'functionValue'
15console.log(objMap.get(anotherKeyObj)); // 'anotherValue'
- 當用物件作為鍵時,重點是必須是「同一個參考」。即使內容相同,只要參考不同,就視為不同的鍵。
迭代(輪詢)
「Map」會保留插入順序,因此常使用迴圈進行遍歷。
以下將介紹如何使用「for...of」、「forEach」及「keys()」、「values()」、「entries()」這些方法。
1// Iterating a Map with for...of and forEach
2const iterMap = new Map([['a', 1], ['b', 2], ['c', 3]]);
3
4// for...of over entries (default)
5for (const [key, value] of iterMap) {
6 console.log(key, value);
7}
8
9// forEach callback
10iterMap.forEach((value, key) => {
11 console.log(key, value);
12});
13
14// keys() and values()
15console.log([...iterMap.keys()]); // ['a','b','c']
16console.log([...iterMap.values()]); // [1,2,3]
- 「entries()」會回傳由「[key, value]」組合而成的陣列,可以用展開語法轉成陣列。請注意,「forEach」回呼函數的參數是「value, key」這個順序。
「Map」與「Object」之間的轉換
可以將現有物件轉換為Map,也可以將Map轉成普通物件或陣列。
1// Convert between Map and Object / Array
2const obj = { a: 1, b: 2 };
3const mapFromObj = new Map(Object.entries(obj)); // Object -> Map
4console.log(mapFromObj.get('a')); // 1
5
6const objFromMap = Object.fromEntries(mapFromObj); // Map -> Object
7console.log(objFromMap); // { a: 1, b: 2 }
8
9const arrayFromMap = [...mapFromObj]; // Map -> Array of [key, value]
10console.log(arrayFromMap); // [['a',1], ['b',2]]
- 使用「Object.entries」及「Object.fromEntries」可以輕鬆完成轉換。但因為物件的鍵僅限於字串或Symbol,轉回物件時,非字串的鍵會遺失。
實用範例:統計頻率(計數Map)
在計算陣列中元素的出現頻率時,使用 Map 能讓這個過程變得更簡單。
以下範例會用Map來統計陣列中字串出現頻率並排序。
1// Count frequencies with Map
2const arr = ['apple','banana','apple','orange','banana','apple'];
3const freq = new Map();
4
5for (const item of arr) {
6 freq.set(item, (freq.get(item) || 0) + 1);
7}
8
9console.log([...freq.entries()]); // [['apple',3], ['banana',2], ['orange',1]]
- 使用「Map」能夠方便進行存在檢查及更新。用物件也能做到,但處理任意型別鍵時,「Map」更加直觀。
「Map」與「JSON.stringify」(序列化)注意事項
「JSON.stringify」無法直接序列化Map。若需儲存Map,必須先將其轉換。
以下範例展示如何將Map轉為陣列、再轉為JSON,以及如何還原。
1// Serialize and deserialize a Map
2const m3 = new Map([['x', 1], ['y', 2]]);
3const json = JSON.stringify([...m3]); // convert to array first
4console.log(json); // '[["x",1],["y",2]]'
5
6const restored = new Map(JSON.parse(json));
7console.log(restored.get('x')); // 1
- 需要儲存或傳送的Map,序列化前應先轉為陣列。還原時,先用「JSON.parse」還原成陣列,再轉為Map。
「WeakMap」介紹與用法
「WeakMap」不同之處在於:其鍵會被弱引用(可由垃圾回收機制回收)。
當用物件作為鍵來儲存快取或中繼資料時很有用,因為當鍵的物件被回收,也會自動釋放對應內容。
1// WeakMap for metadata tied to object lifecycle
2const wm = new WeakMap();
3let obj = {};
4wm.set(obj, { meta: 'info' });
5console.log(wm.get(obj)); // { meta: 'info' }
6
7obj = null; // now the object can be GC'd and its entry removed from WeakMap
- 「WeakMap」無法列舉內容或檢查大小,但有助於防止記憶體洩漏。
總結
「Map」是一種方便的集合,與物件不同之處在於它允許任何型別作為鍵並保留插入順序。從基礎操作到進階用法都能充分理解後,可更靈活且直觀地管理資料。根據用途適當使用「Object」與「Map」,能大幅提升程式碼的清晰度與可讀性。
您可以在我們的 YouTube 頻道上使用 Visual Studio Code 來跟隨上述文章一起學習。 請也查看我們的 YouTube 頻道。