אובייקט `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 בערוץ היוטיוב שלנו. נא לבדוק גם את ערוץ היוטיוב.