Objects

Key/value maps and prototypes

JavaScript Objects

Objects are collections of key-value pairs (properties). They're fundamental to JavaScript—almost everything is an object!

Creating Objects

// Object literal (most common)
const person = {
  name: "Alice",
  age: 30,
  isEmployed: true
};

// Using new Object()
const car = new Object();
car.brand = "Toyota";
car.year = 2024;

// Using Object.create()
const prototype = { greet() { return "Hello!"; } };
const obj = Object.create(prototype);

Accessing Properties

const user = {
  name: "Bob",
  age: 25,
  "favorite color": "blue"  // Keys with spaces need quotes
};

// Dot notation (preferred)
console.log(user.name);  // "Bob"

// Bracket notation (for dynamic keys or special characters)
console.log(user["age"]);  // 25
console.log(user["favorite color"]);  // "blue"

// Dynamic access
const key = "name";
console.log(user[key]);  // "Bob"

Adding, Updating, Deleting Properties

const user = { name: "Alice" };

// Add property
user.age = 30;
user["email"] = "alice@example.com";

// Update property
user.age = 31;

// Delete property
delete user.email;

console.log(user);  // { name: "Alice", age: 31 }

Methods (Functions in Objects)

const person = {
  name: "Alice",
  age: 30,
  
  // Method (traditional)
  greet: function() {
    return `Hello, I'm ${this.name}`;
  },
  
  // Method (shorthand - ES6)
  introduce() {
    return `I'm ${this.name}, ${this.age} years old`;
  },
  
  // Arrow functions don't have their own 'this'
  // Don't use arrow functions for methods!
  badMethod: () => {
    return `Hello, I'm ${this.name}`;  // 'this' is undefined!
  }
};

console.log(person.greet());      // "Hello, I'm Alice"
console.log(person.introduce());  // "I'm Alice, 30 years old"

The 'this' Keyword

this refers to the object the method is called on:

const user = {
  name: "Bob",
  greet() {
    console.log(`Hi, I'm ${this.name}`);
  }
};

user.greet();  // "Hi, I'm Bob" - 'this' is 'user'

// 'this' can be lost when function is detached
const greetFunc = user.greet;
greetFunc();  // Error or undefined - 'this' is not 'user'

// Solution: bind, call, or apply
const boundGreet = user.greet.bind(user);
boundGreet();  // "Hi, I'm Bob"

Object Destructuring

const user = {
  name: "Alice",
  age: 30,
  email: "alice@example.com"
};

// Extract properties into variables
const { name, age } = user;
console.log(name);  // "Alice"
console.log(age);   // 30

// Rename while destructuring
const { name: userName, age: userAge } = user;
console.log(userName);  // "Alice"

// Default values
const { phone = "N/A" } = user;
console.log(phone);  // "N/A"

// Nested destructuring
const person = {
  name: "Bob",
  address: { city: "NYC", zip: "10001" }
};

const { address: { city } } = person;
console.log(city);  // "NYC"

Object Spread & Rest

// Spread: copy properties
const user = { name: "Alice", age: 30 };
const updatedUser = { ...user, age: 31 };
// { name: "Alice", age: 31 }

// Merge objects
const defaults = { theme: "light", lang: "en" };
const userPrefs = { theme: "dark" };
const prefs = { ...defaults, ...userPrefs };
// { theme: "dark", lang: "en" }

// Rest: collect remaining properties
const { name, ...rest } = { name: "Bob", age: 25, city: "NYC" };
console.log(name);  // "Bob"
console.log(rest);  // { age: 25, city: "NYC" }

Useful Object Methods

const user = { name: "Alice", age: 30, city: "NYC" };

// Get keys, values, or entries
Object.keys(user);     // ["name", "age", "city"]
Object.values(user);   // ["Alice", 30, "NYC"]
Object.entries(user);  // [["name", "Alice"], ["age", 30], ["city", "NYC"]]

// Check if property exists
"name" in user;              // true
user.hasOwnProperty("name"); // true

// Freeze (make immutable)
const frozen = Object.freeze({ x: 1 });
frozen.x = 2;  // Ignored in strict mode, error in strict mode

// Assign (merge)
const target = { a: 1 };
const source = { b: 2 };
Object.assign(target, source);  // { a: 1, b: 2 }

Computed Property Names

const key = "favoriteColor";
const value = "blue";

// Old way
const obj1 = {};
obj1[key] = value;

// ES6 way
const obj2 = {
  [key]: value,
  [`${key}_secondary`]: "green"
};
// { favoriteColor: "blue", favoriteColor_secondary: "green" }

Classes (Modern OOP)

class Person {
  // Constructor
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  // Method
  greet() {
    return `Hello, I'm ${this.name}`;
  }
  
  // Getter
  get birthYear() {
    return new Date().getFullYear() - this.age;
  }
  
  // Static method
  static species() {
    return "Homo sapiens";
  }
}

const alice = new Person("Alice", 30);
console.log(alice.greet());      // "Hello, I'm Alice"
console.log(alice.birthYear);    // 1996 (or current year - 30)
console.log(Person.species());   // "Homo sapiens"

// Inheritance
class Employee extends Person {
  constructor(name, age, role) {
    super(name, age);  // Call parent constructor
    this.role = role;
  }
  
  introduce() {
    return `${this.greet()}, I'm a ${this.role}`;
  }
}

const bob = new Employee("Bob", 25, "Developer");
console.log(bob.introduce());  // "Hello, I'm Bob, I'm a Developer"

⚠️ Common Mistakes

  • • Using arrow functions for methods (loses this context)
  • • Forgetting that objects are passed by reference
  • • Modifying objects you don't own (use spread to copy first)
  • • Not checking if a property exists before accessing nested values

✓ Best Practices

  • • Use object destructuring for cleaner code
  • • Use spread operator to copy objects instead of mutating
  • • Use optional chaining (?.) for safe property access
  • • Prefer classes over constructor functions for OOP
  • • Use Object.freeze() for immutable objects