วัตถุ `Map`

วัตถุ `Map`

บทความนี้จะอธิบายเกี่ยวกับวัตถุ Map

เราจะอธิบายทีละขั้นตอน ตั้งแต่การใช้งานพื้นฐานไปจนถึงตัวอย่างที่ใช้ได้จริงในสถานการณ์จริง

YouTube Video

วัตถุ 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 โดยใช้อ็อบเจ็กต์เป็นคีย์

 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'
  • เมื่อใช้อ็อบเจ็กต์เป็นคีย์ จำเป็นอย่างยิ่งที่ต้องเป็น เรฟเฟอเรนซ์เดียวกัน แม้ว่าข้อมูลในอ็อบเจ็กต์จะเหมือนกัน แต่ถ้าเป็นเรฟเฟอเรนซ์คนละตัวกันก็จะนับว่าเป็นคนละคีย์

การวนซ้ำ (looping)

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] ซึ่งสามารถเปลี่ยนเป็นอาร์เรย์ได้ด้วย spread syntax โปรดทราบว่า callback ของ 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 ช่วยให้การแปลงทำได้ง่าย อย่างไรก็ตาม เนื่องจากคีย์ของอ็อบเจ็กต์จะเป็นได้แค่สตริงหรือสัญลักษณ์เท่านั้น เมื่อแปลงกลับเป็นอ็อบเจ็กต์ คีย์ที่ไม่ใช่สตริงจะหายไป

รูปแบบการใช้งานจริง: การนับความถี่ (Count 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 (การทำ Serialization)

JSON.stringify ไม่สามารถ serialize 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 ที่ต้องบันทึกหรือส่งต่อควรแปลงเป็นอาร์เรย์ก่อนการ serialize เมื่อกู้กลับมา ให้ใช้ JSON.parse เพื่อแปลงเป็นอาร์เรย์ แล้วแปลงกลับเป็น Map อีกที

แนะนำ WeakMap และวิธีการใช้งาน

WeakMap แตกต่างกันตรงที่คีย์จะถูกอ้างอิงแบบอ่อน (อาจถูก garbage collection ได้)

มันมีประโยชน์ในการเก็บ cache หรือ เมทาดาต้า โดยใช้อ็อบเจ็กต์เป็นคีย์ และจะถูกลบอัตโนมัติเมื่ออ็อบเจ็กต์คีย์ถูก garbage collection

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 ไม่สามารถนับจำนวนสมาชิกหรือวนซ้ำได้ แต่มีประโยชน์ในการป้องกันปัญหา memory leak

สรุป

Map เป็นคอลเล็กชันที่สะดวก ซึ่งต่างจากอ็อบเจ็กต์ที่อนุญาตให้ใช้คีย์ได้ทุกประเภทและยังคงลำดับการแทรกไว้ หากคุณเข้าใจตั้งแต่การใช้งานพื้นฐานไปจนถึงการประยุกต์ใช้ขั้นสูง คุณจะจัดการข้อมูลได้อย่างยืดหยุ่นและเข้าใจง่ายยิ่งขึ้น หากคุณใช้ Object และ Map ให้เหมาะกับสถานการณ์ จะช่วยให้โค้ดของคุณอ่านง่ายและเข้าใจมากขึ้น

คุณสามารถติดตามบทความข้างต้นโดยใช้ Visual Studio Code บนช่อง YouTube ของเรา กรุณาตรวจสอบช่อง YouTube ด้วย

YouTube Video