לדלג לתוכן

2.7 אובייקטים הרצאה

אובייקטים - Objects

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

אם אתם מכירים מילונים (dictionaries) בפייתון - אובייקטים דומים מאוד, אבל יש כמה הבדלים חשובים.


יצירת אובייקטים - Object Literal

הדרך הנפוצה ביותר ליצור אובייקט היא עם סוגריים מסולסלים:

const person = {
  name: "Alice",
  age: 25,
  city: "Tel Aviv"
};

console.log(person); // { name: "Alice", age: 25, city: "Tel Aviv" }

המפתחות (keys) הם תמיד מחרוזות (או Symbols). הערכים (values) יכולים להיות כל דבר - מספרים, מחרוזות, מערכים, אובייקטים אחרים, פונקציות.

const student = {
  name: "Bob",
  age: 22,
  grades: [90, 85, 92],         // array as value
  address: {                     // nested object
    street: "Herzl 10",
    city: "Haifa"
  },
  isActive: true                 // boolean
};

בפייתון הייתם כותבים:

// Python equivalent:
// person = {
//     "name": "Alice",
//     "age": 25,
//     "city": "Tel Aviv"
// }

ההבדל העיקרי: בפייתון המפתחות חייבים להיות בתוך גרשיים. ב-JavaScript לא צריך (אלא אם המפתח מכיל תווים מיוחדים).


גישה למאפיינים - Accessing Properties

נקודה - Dot Notation

const person = { name: "Alice", age: 25 };

console.log(person.name); // "Alice"
console.log(person.age);  // 25

סוגריים מרובעים - Bracket Notation

console.log(person["name"]); // "Alice"
console.log(person["age"]);  // 25

// useful when the key is in a variable
const key = "name";
console.log(person[key]); // "Alice"

// useful when the key has special characters
const obj = { "full-name": "Alice Smith" };
console.log(obj["full-name"]); // "Alice Smith"
// obj.full-name would cause an error!

בפייתון אפשר לגשת רק עם סוגריים מרובעים (person["name"]). ב-JavaScript יש את שתי הדרכים, והנקודה מועדפת כשאפשר.


הוספה, עדכון ומחיקה של מאפיינים

const car = { brand: "Toyota" };

// adding properties
car.model = "Corolla";
car.year = 2024;
car["color"] = "blue";

console.log(car);
// { brand: "Toyota", model: "Corolla", year: 2024, color: "blue" }

// updating properties
car.year = 2025;

// deleting properties
delete car.color;
console.log(car);
// { brand: "Toyota", model: "Corolla", year: 2025 }

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


קיצור שמות מאפיינים - Shorthand Property Names

כשיש משתנה בשם זהה למפתח שרוצים ליצור, אפשר לקצר:

const name = "Alice";
const age = 25;
const city = "Tel Aviv";

// long version
const person1 = { name: name, age: age, city: city };

// shorthand - same result
const person2 = { name, age, city };

console.log(person2); // { name: "Alice", age: 25, city: "Tel Aviv" }

זה מאוד נפוץ ב-JavaScript מודרני, במיוחד בפונקציות שמחזירות אובייקטים.


שמות מאפיינים מחושבים - Computed Property Names

אפשר להשתמש בביטוי כשם של מאפיין:

const key = "name";
const obj = { [key]: "Alice" };
console.log(obj); // { name: "Alice" }

// useful for dynamic keys
const field = "email";
const value = "alice@test.com";
const data = { [field]: value };
console.log(data); // { email: "alice@test.com" }

// can use expressions
const prefix = "user";
const userObj = {
  [`${prefix}Name`]: "Alice",
  [`${prefix}Age`]: 25
};
console.log(userObj); // { userName: "Alice", userAge: 25 }

מתודות - Methods

מתודות הן פונקציות שמוגדרות בתוך אובייקט:

const calculator = {
  // method - short syntax
  add(a, b) {
    return a + b;
  },

  // method - longer syntax (also works)
  subtract: function(a, b) {
    return a - b;
  },

  // arrow function as method (be careful with "this"!)
  multiply: (a, b) => a * b
};

console.log(calculator.add(5, 3));      // 8
console.log(calculator.subtract(10, 4)); // 6
console.log(calculator.multiply(3, 7));  // 21

מילת המפתח this

בתוך מתודה של אובייקט, this מתייחס לאובייקט עצמו:

const person = {
  name: "Alice",
  age: 25,

  greet() {
    console.log("Hi, I'm " + this.name);
  },

  haveBirthday() {
    this.age += 1;
    console.log(this.name + " is now " + this.age);
  }
};

person.greet();        // "Hi, I'm Alice"
person.haveBirthday(); // "Alice is now 26"

שימו לב - this עובד רק עם פונקציות רגילות (function declaration / method shorthand). עם arrow functions, this לא מתייחס לאובייקט:

const person2 = {
  name: "Bob",

  // this works
  greet() {
    console.log("Hi, I'm " + this.name);
  },

  // this does NOT work as expected
  greetArrow: () => {
    console.log("Hi, I'm " + this.name); // this is NOT the object!
  }
};

person2.greet();      // "Hi, I'm Bob"
person2.greetArrow(); // "Hi, I'm undefined"

כלל אצבע: כשרוצים להשתמש ב-this - משתמשים בפונקציה רגילה, לא arrow function.
בפייתון המקביל הוא self, אבל שם חייבים להעביר אותו כפרמטר ראשון. ב-JavaScript this זמין אוטומטית.


Object.keys, Object.values, Object.entries

מתודות סטטיות שמחזירות מערכים של המפתחות, הערכים, או הזוגות:

const person = { name: "Alice", age: 25, city: "Tel Aviv" };

// keys - array of all keys
console.log(Object.keys(person));   // ["name", "age", "city"]

// values - array of all values
console.log(Object.values(person)); // ["Alice", 25, "Tel Aviv"]

// entries - array of [key, value] pairs
console.log(Object.entries(person));
// [["name", "Alice"], ["age", 25], ["city", "Tel Aviv"]]

זה מאוד שימושי למעבר על אובייקט:

// iterate over keys and values
Object.entries(person).forEach(([key, value]) => {
  console.log(`${key}: ${value}`);
});
// name: Alice
// age: 25
// city: Tel Aviv

בפייתון זה מקביל ל:

// Python equivalent:
// person.keys()    -> dict.keys()
// person.values()  -> dict.values()
// person.items()   -> dict.items()  (not "entries"!)

Object.assign - מיזוג אובייקטים

מאפשר להעתיק מאפיינים מאובייקט אחד לאחר:

const defaults = { color: "blue", size: "medium", theme: "dark" };
const userPrefs = { color: "red", size: "large" };

// merge - userPrefs overrides defaults
const settings = Object.assign({}, defaults, userPrefs);
console.log(settings);
// { color: "red", size: "large", theme: "dark" }

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


אופרטור הפיזור לאובייקטים - Spread Operator

דרך מודרנית יותר למזג אובייקטים:

const defaults = { color: "blue", size: "medium", theme: "dark" };
const userPrefs = { color: "red", size: "large" };

// spread - same result as Object.assign
const settings = { ...defaults, ...userPrefs };
console.log(settings);
// { color: "red", size: "large", theme: "dark" }

// can add new properties too
const fullSettings = { ...settings, language: "he", fontSize: 16 };

ב-spread, הסדר חשוב - מי שבא אחרון "מנצח". אפשר לחשוב על זה כאילו כל אובייקט "נשפך" לתוך החדש, ומה שנשפך אחרון דורס את מה שהיה לפניו.


Object.freeze ו-Object.seal

Object.freeze - הקפאה מוחלטת

const config = { host: "localhost", port: 3000 };
Object.freeze(config);

config.port = 8080;      // does nothing (silently fails)
config.newProp = "hello"; // does nothing
delete config.host;       // does nothing

console.log(config); // { host: "localhost", port: 3000 } - unchanged

Object.seal - חסימת מבנה

const settings = { color: "blue", size: "medium" };
Object.seal(settings);

settings.color = "red";    // works - can update existing
settings.newProp = "hello"; // does nothing - can't add
delete settings.color;      // does nothing - can't delete

console.log(settings); // { color: "red", size: "medium" }

ההבדל: freeze לא מאפשר שום שינוי. seal מאפשר לעדכן ערכים קיימים אבל לא להוסיף או למחוק מאפיינים.

שימו לב - שניהם עובדים ברמה שטחית בלבד (shallow). אובייקטים מקוננים לא מוגנים.


אופרטור in ו-hasOwnProperty

אופרטור in

בודק אם מפתח קיים באובייקט:

const person = { name: "Alice", age: 25 };

console.log("name" in person);  // true
console.log("email" in person); // false

hasOwnProperty

בודק אם מפתח קיים באובייקט עצמו (לא בשרשרת הפרוטוטייפים):

const person = { name: "Alice", age: 25 };

console.log(person.hasOwnProperty("name")); // true
console.log(person.hasOwnProperty("toString")); // false (inherited)
console.log("toString" in person); // true (exists on prototype)

ברוב המקרים in מספיק, אבל hasOwnProperty שימושי כשרוצים לבדוק רק את המאפיינים הישירים של האובייקט.

בפייתון הייתם בודקים עם "name" in person למילון, או hasattr(obj, "name") לאובייקט.


השוואת אובייקטים - Comparison

אובייקטים מושווים לפי הפניה (reference), לא לפי ערך:

const a = { name: "Alice" };
const b = { name: "Alice" };
const c = a;

console.log(a === b); // false - different objects in memory
console.log(a === c); // true - same reference

זה בדיוק כמו בפייתון:

// Python:
// a = {"name": "Alice"}
// b = {"name": "Alice"}
// a == b  -> True  (compares values)
// a is b  -> False (compares references)

אבל יש הבדל חשוב: בפייתון == משווה ערכים, ו-is משווה הפניות. ב-JavaScript, === משווה הפניות לאובייקטים - אין דרך מובנית להשוות ערכים של אובייקטים.

אם רוצים להשוות תוכן, צריך לעשות את זה ידנית:

// simple shallow comparison
function areEqual(obj1, obj2) {
  const keys1 = Object.keys(obj1);
  const keys2 = Object.keys(obj2);
  if (keys1.length !== keys2.length) return false;
  return keys1.every((key) => obj1[key] === obj2[key]);
}

// quick hack for deep comparison (not recommended for production)
JSON.stringify(a) === JSON.stringify(b); // true - but has limitations

אובייקטים מקוננים - Nested Objects

const company = {
  name: "TechCorp",
  address: {
    street: "Rothschild 1",
    city: "Tel Aviv",
    country: "Israel"
  },
  employees: [
    { name: "Alice", role: "developer" },
    { name: "Bob", role: "designer" }
  ]
};

// accessing nested properties
console.log(company.address.city);        // "Tel Aviv"
console.log(company.employees[0].name);   // "Alice"
console.log(company.employees[1].role);   // "designer"

שרשור אופציונלי - Optional Chaining

מה קורה כשמנסים לגשת למאפיין שלא קיים?

const user = { name: "Alice" };

console.log(user.address);       // undefined
console.log(user.address.city);  // ERROR! Cannot read property 'city' of undefined

אופרטור ?. (optional chaining) פותר את הבעיה:

const user = { name: "Alice" };

console.log(user.address?.city);     // undefined (no error)
console.log(user.profile?.avatar);   // undefined (no error)

// can chain multiple levels
const company = {
  ceo: {
    name: "Bob"
    // no address property
  }
};
console.log(company.ceo?.address?.city); // undefined (no error)

אפשר להשתמש גם עם מתודות ומערכים:

const obj = {};

obj.someMethod?.();    // won't crash if someMethod doesn't exist
obj.arr?.[0];          // won't crash if arr doesn't exist

בפייתון אין מקביל ישיר. הייתם צריכים:

// Python equivalent:
// city = user.get("address", {}).get("city")
// or
// city = user["address"]["city"] if "address" in user else None

ההבדל בין אובייקטים ל-דיקשנרי בפייתון

JavaScript Object Python Dict
תחביר { name: "Alice" } { "name": "Alice" }
גישה obj.name או obj["name"] dict["name"] בלבד
מפתחות מחרוזות (או Symbols) כל טיפוס hashable
מתודות יכול להכיל פונקציות בדרך כלל לא
prototype יש שרשרת ירושה אין
השוואה לפי הפניה בלבד == משווה ערכים

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


דפוסים נפוצים

המרת מערך של זוגות לאובייקט

const pairs = [["name", "Alice"], ["age", 25], ["city", "Tel Aviv"]];
const obj = Object.fromEntries(pairs);
console.log(obj); // { name: "Alice", age: 25, city: "Tel Aviv" }

מעבר על אובייקט עם for...in

const person = { name: "Alice", age: 25, city: "Tel Aviv" };

for (const key in person) {
  console.log(`${key}: ${person[key]}`);
}
// name: Alice
// age: 25
// city: Tel Aviv

שכפול אובייקט

const original = { name: "Alice", age: 25 };

// shallow copy with spread
const copy = { ...original };

// shallow copy with Object.assign
const copy2 = Object.assign({}, original);

copy.name = "Bob";
console.log(original.name); // "Alice" - not affected

בדיקה אם אובייקט ריק

const obj = {};

const isEmpty = Object.keys(obj).length === 0;
console.log(isEmpty); // true

סיכום

  • אובייקטים הם אוספים של זוגות מפתח-ערך, דומים למילונים בפייתון
  • גישה עם נקודה (obj.key) או סוגריים (obj["key"])
  • ניתן להוסיף, לעדכן ולמחוק מאפיינים בכל רגע
  • קיצורים: shorthand properties, computed properties
  • מתודות הן פונקציות בתוך אובייקט, this מתייחס לאובייקט
  • Object.keys(), .values(), .entries() למעבר על אובייקט
  • spread ({...obj}) ו-Object.assign() למיזוג והעתקה
  • Object.freeze() ו-Object.seal() למניעת שינויים
  • אובייקטים מושווים לפי הפניה, לא לפי ערך
  • ?. (optional chaining) למניעת שגיאות בגישה למאפיינים מקוננים