1. דף הבית
  2. קורסים אונליין
  3. קורס Javascript אונליין
  4. מבני נתונים מתקדמים: Object, Map, Set

מבני נתונים מתקדמים: Object, Map, Set

נוסף על מערכים, שפת ג'אווה סקריפט מספקת מספר מבני נתונים מובנים שעוזרים באחסון ובניהול נתונים בצורה יעילה. במדריך זה נסקור את מבני הנתונים אובייקט, מפה, סט (קבוצה) וכן WeakMap ו-WeakSet, נכיר שיטות עבודה מומלצות ונציג טיפים לעבודה יעילה איתם.
Object, Map, Set ב-JS

מבני נתונים מתקדמים בג'אווה סקריפט

מבני נתונים חיוניים לארגון וניהול נתונים ביעילות. בשפת JavaScript קיימים מספר מבני נתונים מובנים המאפשרים למפתחים לאחסן, לגשת ולתפעל נתונים בדרכים שונות.

בפרק הקודם למדנו על מערכים ועל דרכים לממש באמצעותם מחסנית ותור. מערך הוא מבנה הנתונים הנפוץ ביותר, אך לצידו קיימים מבני נתונים נוספים שכדאי להכיר, ואותם נסקור בפרק זה.

חשוב להבין שבעת התכנות, שימוש במבנה הנתונים המתאים ביותר בכל מצב יכול לשפר את קריאות הקוד, להקל על התכנות והתחזוקה השוטפים, להאיץ את מהירות הביצוע של הקוד ועוד. לכן, כאשר נדרש להגדיר מבנה נתונים בקוד, רצוי להקדיש דקה של מחשבה לבחירת מבנה הנתונים המתאים ביותר לפני התחלת התכנות.

אלו הם מבני הנתונים הנפוצים ב-JavaScript:

  • מערך (Array) - אידיאלי עבור אוספים מסודרים של אלמנטים שבהם הגישה לאלמנטים מתבצעת באמצעות אינדקס. מתאים לאחסון רשימות של פריטים.
  • אובייקט (Object) - אידאלי עבור נתונים מובנים עם מאפיינים בעלי שם. שימוש באובייקטים טוב כאשר רוצים לייצג ישויות בעולם האמיתי עם צמדי מפתח-ערך (key-value).
  • מפה (Map) - אידאלי כאשר המפתחות אינם מוגבלים למחרוזות ונדרשות תוספות/מחיקות תכופות. מפות מציעות גמישות רבה יותר עם סוגי מפתח בהשוואה לאובייקטים.
  • סט (Set) - אידיאלי לאחסון ערכים ייחודיים וללא כפילויות. סטים מבטיחים שכל ערך קיים פעם אחת בלבד.
  • מפה מוחלשת (WeakMap) - אידאלי לשיוך נתונים לאובייקטים (מבנה נתונים מסוג Object), תוך שימוש באיסוף אשפה (Garbage Collection). שימושי כאשר צריך לצרף מטא-נתונים לאובייקטים, מבלי להשפיע על מחזור החיים שלהם.
  • סט מוחלש (WeakSet) - אידאלי לאחסון אובייקטים ייחודיים (מבנה נתונים מסוג Object), תוך שימוש באיסוף אשפה. WeakSets מחזיקים רק אובייקטים ואינם מונעים את איסוף האשפה שלהם.

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

אובייקטים בג'אווה סקריפט

אובייקטים הם אוספים של זוגות מפתח-ערך (key-value), כאשר המפתחות הם מחרוזות או סמלים והערכים יכולים להיות מכל סוג. אובייקטים הם אבני הבניין הבסיסיות למידול ישויות בעולם האמיתי ב-JavaScript.

כל זוג מפתח-ערך מכונה רשומה (entry).

נדגים יצירת אובייקט להלן.

// Using object literal
let person = {
    name: 'John Doe',
    age: 30,
    job: 'Developer'
};

// Using Object constructor
let car = new Object();
car.make = 'Toyota';
car.model = 'Camry';
car.year = 2021;

בדוגמה זו, הדגמנו 2 דרכים להגדיר אובייקט:

1. הגדרה ישירה של אובייקט הכוללת אתחול - הגדרנו אובייקט בשם person שממדל איש. יש לו 3 רשומות של מפתח-ערך: המפתח name עם הערך John Doe, המפתח age עם הערך 30, המפתח job עם הערך Developer. כל המפתחות הם מסוג מחרוזת כפי שמתחייב בהגדרת אובייקטים. נשים לב שהגדרת האובייקט person כבר כוללת את הרשומות, או במילים אחרות: האובייקט person מאותחל בזמן ההגדרה שלו.

2. הגדרת אובייקט והוספה ערכים לאחר מכן - הגדרנו את האובייקט car שממדל מכונית. ההגדרה אינה כוללת אתחול של רשומות כלשהן. לאחר מכן, הוספנו את המפתחות והערכים השונים: תחילה קבענו את המפתח make עם הערך Toyota, לאחר מכן קבענו את הערך model עם המפתח Camry ובסוף קבענו את הערך year עם הערך 2021.

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

ניתן לשלב בין 2 השיטות: להגדיר את האובייקט עם רשומות ערך-מפתח התחלתיים, ובהמשך הקוד לשנות את הרשומות האלו או להוסיף חדשות.

 

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

console.log(person.name); // John Doe
console.log(person['age']); // 30

console.log(car.make); // Toyota
console.log(car['model']); // Camry

בדוגמה זו, הדגמנו גישה לערכים של המפתח name ושל המפתח age באובייקט person, וכן לערכים של המפתח make ושל המפתח model של האובייקט car.

 

קיימות מתודות (Methods) למחלקה Object, אשר מקבלות כפרמטר אובייקט ומחזירות ערך כלשהו.

המתודות הנפוצות של Object:

keys - מחזירה מערך עם כל המפתחות של האובייקט.

values - מחזירה מערך עם כל הערכים של האובייקט.

entries - מחזירה מערך דו מימדי. במערך זה כל איבר מייצג רשומה והוא מערך עם 2 איברים: מפתח וערך.

על מנת לדעת את כמות האיברים (הרשומות) באובייקט, נבדוק את כמות המפתחות שלו באמצעות התכונה length.

 

דוגמה:

Object.keys(person);  // ['name', 'age', 'job']
Object.values(person);  // ['John Doe', 30, 'Developer']
Object.entries(person);  // [['name', 'John Doe'], ['age', 30], ['job', 'Developer']]

Object.keys(person).length  // 3

מבנה הנתונים Map

במבנה הנתונים Object, המפתח חייב להיות מחרוזת. מבנה הנתונים Map (מפה) מאפשר גמישות בבחירת המפתח, ומאפשר קביעת רשומות (זוגות של מפתח-ערך), כאשר המפתח יכול להיות מחרוזת, מספר, ערך בוליאני וכן הלאה.

נדגים שימוש במבנה הנתונים Map להלן.

let map = new Map();

// Adding entries
map.set('name', 'Alice');
map.set(1, 'one');
map.set(true, 'boolean value');

בדוגמה זו, הגדרנו מפה בשם map. לאחר מכן, השתמשנו במתודה set והגדרנו את הזוגות הבאים: מפתח name עם הערך Alice, מפתח 1 עם הערך one, מפתח true עם הערך boolean value.

 

בעבודה עם מבנה הנתונים Map משתמשים במתודות הבאות:

get - מחזירה ערך של מפתח נתון.

set - מגדירה זוג מפתח-ערך חדש, או מעדכנת ערך עבור מפתח קיים.

has - בודקת האם קיים מפתח נתון.

delete - מוחקת רשומה לפי מפתח נתון. במקרה של הצלחה (הערך היה קיים לפני המחיקה ונמחק) מוחזר true, אחרת מוחזר false.

clear - מוחקת את כל הרשומות.

התכונה size מחזירה את גודל מבנה הנתונים, כלומר, את כמות המפתחות שלו.

דוגמה:

console.log(map.get('name')); // Alice
console.log(map.get(1)); // one

map.set('name', 'Bob'); // Modify entry
console.log(map.get('name')); // Bob

console.log(map.size); // 3

בדוגמה זו, השתמשנו במתודה get כדי להדפיס בקונסול את ערך המפתח name ואת ערך המפתח 1. לאחר מכן, שינינו את ערך המפתח name שיהיה Bob והדפסנו אותו בקונסול. לסיום, הדפסנו את גודל מבנה הנתונים.

 

נראה דוגמה נוספת שעושה שימוש במתודות has, delete, clear.

// Create a new Map
let userMap = new Map();

// Add entries to the Map
userMap.set('name', 'Alice');
userMap.set('age', 30);
userMap.set('email', 'alice@example.com');

console.log('Initial Map:', userMap);

// Check if specific keys exist
console.log('Has "name"?', userMap.has('name')); // true
console.log('Has "gender"?', userMap.has('gender')); // false

// Delete an entry by key
let deleteResult = userMap.delete('age');
console.log('Deleted "age"?', deleteResult); // true
console.log('Has "age"?', userMap.has('age')); // false

// Clear all entries from the Map
userMap.clear();
console.log('Map after clear:', userMap); // Map(0) {}
console.log('Has "name"?', userMap.has('name')); // false
console.log('Has "email"?', userMap.has('email')); // false

מבנה הנתונים Set

סט (לעיתים נקרא גם "קבוצה") הוא אוסף של ערכים ייחודיים. סטים שימושיים לאחסון אלמנטים ללא כפילויות, כלומר, כל אלמנט יכול להופיע פעם אחת בלבד.

נדגים שימוש במבנה הנתונים Set להלן.

let set = new Set();

// Adding values
set.add(1);
set.add(2);
set.add(2); // Duplicate, will be ignored
set.add('hello');

בדוגמה זו, הגדרנו את הקבוצה set. הוספנו לה את הערך 1. לאחר מכן, הוספנו את הערך 2. לאחר מכן, הוספנו שוב את הערך 2, אבל הערך לא התווסף כיוון שהוא כבר קיים. לא נוצרת שגיאה כלשהי, אלא פשוט התעלמות מההוספה. לסיום, הוספנו את המחרוזת hello.

 

בעבודה עם מבנה הנתונים Set משתמשים במתודות הבאות:

add - מוסיפה ערך.

delete - מוחקת ערך. במקרה של הצלחה (הערך היה קיים לפני המחיקה ונמחק) מוחזר true, אחרת מוחזר false.

has - בודקת אם קיים ערך נתון.

clear - מוחקת את כל הערכים.

התכונה size מחזירה את גודל מבנה הנתונים, כלומר, את כמות הערכים שבו.

דוגמה:

let mySet = new Set();

// Adding values
mySet.add(7);
mySet.add(7); // Duplicate, will be ignored
mySet.add('Green');

console.log(mySet.has(1)); // false
console.log(mySet.has(7)); // true
console.log(mySet.size); // 2

console.log(mySet.delete(7)); // Remove value and return true
console.log(mySet.has(7)); // false

console.log(mySet.delete('Joe')); // Remove non existing value will be ignored and return false

mySet.clear(); // Remove all elements from the Set

בדוגמה זו הגדרנו את הסט mySet. הוספנו לו את הערך 7. הוספנו לו שוב את הערך 7, פעולה שלא בוצעה (כי סט מחזיק עותק יחיד מכל ערך). הוספנו לו את הערך Green. בדקנו האם 1 נמצא בסט והאם 7 נמצא בסט. בדקנו את גודל הסט וקיבלנו 2. מחקנו את הערך 7 וקיבלנו true, כיוון שהערך היה בסט ונמחק. בדקנו האם הערך 7 קיים בסט לאחר המחיקה וקיבלנו false. מחקנו את הערך Joe מהסט וקיבלנו false, כיוון שערך זה לא היה מלכתחילה בסט. לסיום, מחקנו את כל הערכים בסט.

מבנה הנתונים WeakMap

בדומה למבנה הנתונים Map, גם WeakMap מחזיק ברשומות של מפתח-ערך. עם זאת, המפתחות ב-WeakMap הן אובייקטים שניתן להפעיל עליהם "איסוף זבל" (Garbage Collection).

במילים אחרות, הרפרנס של המפתח ב-WeakMap הוא חלש, וברגע שלא נעשה יותר שימוש במפתח, אז JavaScript מוחקים אותו ובכך חוסכים זכרון.

דוגמה:

// Create a new WeakMap
let weakMap = new WeakMap();

// Add an object to the WeakMap
let obj = { name: 'John' };
weakMap.set(obj, 'Developer');

// Access the WeakMap
console.log(weakMap.get(obj)); // Developer

obj = null; // The key object can now be garbage collected
console.log(weakMap.get(obj)); // undefined

בדוגמה זו, הגדרנו מבנה נתונים מסוג WeakMap ששמו weakMap. לאחר מכן, הגדרנו אובייקט obj שכולל את הרשומה עם המפתח name והערך John. לאחר מכן, יצרנו רשומה חדשה במבנה הנתונים weakMap עם המפתח שהוא רפרנס ל-obj והערך Developer. בהמשך, הדפסנו לקונסול את הערך של המפתח obj במבנה הנתונים weakMap. בסיום, הכנסנו למשתנה obj את הערך null, ובכך למעשה מחקנו את האובייקט שהוגדר קודם לכן ומחקנו גם את המפתח המתאים ב-weakMap. כאשר אנו מנסים בשנית להדפיס את הערך של המפתח obj במבנה הנתונים weakMap אנו מקבלים undefined.

 

בעבודה עם מבנה הנתונים WeakMap משתמשים במתודות הבאות:

get - מחזירה ערך של מפתח נתון.

set - מגדירה זוג מפתח-ערך חדש, או מעדכנת ערך עבור מפתח קיים.

has - בודקת האם קיים מפתח נתון.

delete - מוחקת רשומה לפי מפתח נתון. במקרה של הצלחה (הערך היה קיים לפני המחיקה ונמחק) מוחזר true, אחרת מוחזר false.

למבנה הנתונים WeakMap אין מתודה clear ואין תכונה size. זאת מכיוון שהרשומות נמחקות ממבנה הנתונים בצורה אוטומטית (באמצעות מנגנון ה-Garbage Collection), כאשר אין בהן יותר שימוש.

דוגמה:

// Create a new WeakMap
let userWeakMap = new WeakMap();

// Create objects to use as keys
let obj1 = {name: 'Alice'};
let obj2 = {name: 'Bob'};

// Add elements to the WeakMap
userWeakMap.set(obj1, 'Admin');
userWeakMap.set(obj2, 'User');

// Access the WeakMap
console.log(userWeakMap.get(obj1)); // Admin

// Check if specific keys exist
console.log('Initial WeakMap:');
console.log('Has obj1?', userWeakMap.has(obj1)); // true
console.log('Has obj2?', userWeakMap.has(obj2)); // true

// Delete an entry by key
let deleteResult = userWeakMap.delete(obj1);
console.log('Deleted obj1?', deleteResult); // true
console.log('Has obj1?', userWeakMap.has(obj1)); // false

// Check for a non-existing key
let obj3 = {name: 'Charlie'};
console.log('Has obj3?', userWeakMap.has(obj3)); // false

// Attempt to delete a non-existing key
deleteResult = userWeakMap.delete(obj3);
console.log('Deleted obj3?', deleteResult); // false

// Final state of the WeakMap
console.log('Final WeakMap:');
console.log('Has obj2?', userWeakMap.has(obj2)); // true

מבנה הנתונים WeakSet

בדומה למבנה הנתונים Set, גם WeakSet מחזיק באוסף של ערכים ייחודיים. עם זאת, הערכים ב-WeakSet הם אובייקטים שניתן להפעיל עליהם "איסוף זבל" (Garbage Collection) כאשר לא נעשה בהם שימוש, כמו ב-WeakMap.

דוגמה:

// Create a new WeakSet
let weakSet = new WeakSet();

// Add objects to the WeakSet
let obj1 = { id: 1 };
let obj2 = { id: 2 };
weakSet.add(obj1);
weakSet.add(obj2);

// Access the WeakSet
console.log(weakSet.has(obj1)); // true

obj1 = null; // The object can now be garbage collected
console.log(weakSet.has(obj1)); // false

בדוגמה זו, הגדרנו מבנה נתונים מסוג WeakSet ששמו weakSet. לאחר מכן, הגדרנו שני אובייקטים: הראשון obj1 עם המפתח id והערך 1, והשני obj2 עם המפתח id והערך 2. לאחר מכן, הוספנו את 2 האובייקטים האלו כערכים חדשים במבנה הנתונים weakSet. בהמשך, בדקנו האם האובייקט obj1 נמצא במבנה הנתונים weakSet והדפסנו לקונסול true. בסיום, הכנסנו למשתנה obj1 את הערך null, ובכך למעשה מחקנו את האובייקט שהוגדר קודם לכן ומחקנו גם את הערך המתאים ב-weakSet. בדקנו בשנית אם האובייקט obj1 נמצא במבנה הנתונים weakSet והדפסנו לקונסול false.

 

בעבודה עם מבנה הנתונים WeakSet משתמשים במתודות הבאות:

add - מוסיפה ערך חדש.

has - בודקת האם קיים ערך נתון.

delete - מוחקת ערך נתון. במקרה של הצלחה (הערך היה קיים לפני המחיקה ונמחק) מוחזר true, אחרת מוחזר false.

למבנה הנתונים WeakSet אין מתודה clear ואין תכונה size. זאת מכיוון שהערכים נמחקים ממבנה הנתונים בצורה אוטומטית (באמצעות מנגנון ה-Garbage Collection), כאשר אין בהן יותר שימוש.

דוגמה:

// Create a new WeakSet
let userWeakSet = new WeakSet();

// Create objects to add to the WeakSet
let obj1 = { name: 'Alice' };
let obj2 = { name: 'Bob' };
let obj3 = { name: 'Charlie' };

// Add objects to the WeakSet
userWeakSet.add(obj1);
userWeakSet.add(obj2);

console.log('WeakSet has obj1?', userWeakSet.has(obj1)); // true
console.log('WeakSet has obj2?', userWeakSet.has(obj2)); // true
console.log('WeakSet has obj3?', userWeakSet.has(obj3)); // false

// Delete an object from the WeakSet
userWeakSet.delete(obj1);

console.log('WeakSet has obj1 after deletion?', userWeakSet.has(obj1)); // false
console.log('WeakSet has obj2 after deletion?', userWeakSet.has(obj2)); // true

// Trying to delete a non-existent object
let deleteResult = userWeakSet.delete(obj3);
console.log('Trying to delete obj3 (non-existent):', deleteResult); // false

השוואה בין מבני הנתונים השונים בג'אווה סקריפט

הכנו עבורכם טבלה המשווה בין מבני הנתונים השונים ב-JavaScript.

מבנה הנתונים מפתח ערך מניעת כפילויות סדר האלמנטים איסוף זבל
Object מחרוזת, סמל הכל לא אין סדר לא
Map הכל הכל לא לפי סדר ההכנסה לא
Set לא קיים הכל כן לפי סדר ההכנסה לא
WeakMap אובייקט הכל לא לא מוגדר כן - למפתחות
WeakSet לא קיים אובייקט כן לא מוגדר כן - לערכים

 

כאשר נדרשת גישה מהירה לערכים על בסיס מפתחות, והמפתחות יכולים להיות מחרוזות, נעדיף שימוש ב-Object. אם המפתחות יכולים להיות מכל סוג, נשתמש ב-Map.

כאשר אין מפתחות, אלא רק ערכים ייחודיים ללא כפילויות, נשתמש ב-Set.

ב-WeakMap וב-WeakSet נשתמש בעיקר כאשר עובדים עם אובייקטים (מפתח ב-WeakMap או ערך ב-WeakSet) ואם אין בעיה שיופעל Garbage Collection בצורה אוטומטית כאשר האובייקט כבר אינו בשימוש.

הוספת תגובה
אנו משתמשים בעוגיות על מנת לשפר את חווית המשתמש באתר. מדיניות הפרטיותאני מסכים