אובייקט `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 הוא שניתן להשתמש באובייקטים ישירות כמפתחות.

הדוגמה הבאה מציגה כיצד לשייך ערכים במפה תוך שימוש באובייקטים כמפתחות.

 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], שניתן להמיר למערך בעזרת תחביר ה-spread. יש לשים לב שה-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 מפשט את התהליך.

הקוד הבא הוא דוגמה לשימוש ב-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
  • Maps שיש לשמור או לשלוח צריכים להיות מומרות למערכים לפני הסיריאליזציה. בעת השחזור, השתמש ב-JSON.parse להמרה למערך, ואז החזר אותו ל-Map.

הקדמה ל-WeakMap וכיצד להשתמש בו

WeakMap שונה בכך שהמפתחות בו הם ברפרנס חלש (נתונים לאיסוף זבל).

הוא שימושי להחזיק מטמון או metadata עם אובייקטים כמפתחות, ומשחרר אותם אוטומטית כאשר אובייקט המפתח נאסף.

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 בהתאם למקרה השימוש, ניתן לשפר משמעותית את בהירות הקוד וקריאותו.

תוכלו לעקוב אחר המאמר שלמעלה באמצעות Visual Studio Code בערוץ היוטיוב שלנו. נא לבדוק גם את ערוץ היוטיוב.

YouTube Video