Home Module 12 Objects

1. Introduction

An object is a collection of named values called properties. Each property has a key (a string or symbol) and a value (any type). Objects let you group related data together: a user has a name, email, age, and role — all in one object instead of four separate variables.

Objects are reference types. Almost everything in JavaScript is an object or based on an object prototype.

2. Theory

2.1 Creating objects

// Object literal — the most common way
const user = {
  name:  'Alice',
  age:   28,
  email: 'alice@example.com',
  active: true
};

// Empty object, then add properties
const config = {};
config.theme   = 'dark';
config.lang    = 'en';
config.version = 2;

// Object constructor (rarely needed)
const obj = new Object();
obj.x = 10;

2.2 Accessing properties

const product = { name: 'Laptop', price: 999, 'in-stock': true };

// Dot notation — simple, most common
console.log(product.name);  // 'Laptop'
console.log(product.price); // 999

// Bracket notation — required when key has special chars, spaces, or is a variable
console.log(product['in-stock']); // true
console.log(product['name']);     // 'Laptop' — works too

// Dynamic key via variable
const key = 'price';
console.log(product[key]); // 999 — uses the value of key

// Nested objects
const order = {
  id: 1,
  customer: {
    name: 'Bob',
    address: {
      city: 'London'
    }
  }
};
console.log(order.customer.name);         // 'Bob'
console.log(order.customer.address.city); // 'London'

2.3 Adding, updating, and deleting properties

const car = { make: 'Toyota', model: 'Corolla' };

// Add new property
car.year  = 2023;
car.color = 'white';

// Update existing
car.model = 'Camry';

// Delete a property
delete car.color;

console.log(car); // { make: 'Toyota', model: 'Camry', year: 2023 }

// Check if property exists
console.log('make' in car);   // true
console.log('color' in car);  // false (was deleted)
console.log(car.hasOwnProperty('year')); // true

2.4 Shorthand property names

// When variable name matches the property name
const name  = 'Alice';
const age   = 28;
const email = 'alice@example.com';

// Long form
const user1 = { name: name, age: age, email: email };

// Shorthand — use when key and variable name are the same
const user2 = { name, age, email }; // identical result

2.5 Computed property names

// Dynamic keys at creation time
const field   = 'username';
const profile = { [field]: 'alice_dev' };
console.log(profile); // { username: 'alice_dev' }

// Useful for building objects dynamically
function setField(obj, key, value) {
  return { ...obj, [key]: value }; // create new object with updated field
}
const updated = setField({ a: 1, b: 2 }, 'c', 3);
console.log(updated); // { a: 1, b: 2, c: 3 }

2.6 Destructuring

const user = { name: 'Alice', age: 28, role: 'admin', active: true };

// Extract into variables (names must match keys)
const { name, age } = user;
console.log(name); // 'Alice'
console.log(age);  // 28

// Rename while destructuring
const { name: userName, role: userRole } = user;
console.log(userName); // 'Alice'
console.log(userRole); // 'admin'

// Default values
const { theme = 'light', lang = 'en' } = user;
console.log(theme); // 'light' — user.theme doesn't exist, uses default

// In function parameters
function greet({ name, role }) {
  console.log(`Hello ${name}, you are a ${role}`);
}
greet(user); // "Hello Alice, you are a admin"

// Nested destructuring
const { customer: { name: custName } } = { customer: { name: 'Bob' } };
console.log(custName); // 'Bob'

2.7 Spread and Object.assign — copying objects

const original = { a: 1, b: 2, c: 3 };

// Spread — shallow copy
const copy = { ...original };
copy.a = 99;
console.log(original.a); // 1 — unaffected

// Merge objects (right side wins on conflict)
const defaults = { theme: 'light', lang: 'en', size: 'md' };
const custom   = { theme: 'dark', size: 'lg' };
const merged   = { ...defaults, ...custom };
// { theme: 'dark', lang: 'en', size: 'lg' }

// Add/override single property immutably
const user = { name: 'Alice', age: 28 };
const older = { ...user, age: 29 }; // user unchanged
console.log(older); // { name: 'Alice', age: 29 }

2.8 Iterating over objects

const scores = { Alice: 95, Bob: 87, Carol: 92 };

// Object.keys — array of keys
Object.keys(scores).forEach(name => {
  console.log(`${name}: ${scores[name]}`);
});

// Object.values — array of values
const values = Object.values(scores);
console.log(values); // [95, 87, 92]

const avg = values.reduce((sum, v) => sum + v, 0) / values.length;
console.log(avg.toFixed(1)); // '91.3'

// Object.entries — array of [key, value] pairs
Object.entries(scores).forEach(([name, score]) => {
  console.log(`${name} scored ${score}`);
});

// Convert object to sorted array
const sorted = Object.entries(scores)
  .sort((a, b) => b[1] - a[1])  // sort by value descending
  .map(([name, score]) => `${name}: ${score}`);
console.log(sorted); // ['Alice: 95', 'Carol: 92', 'Bob: 87']

2.9 Optional chaining on objects

const user = {
  name: 'Alice',
  address: {
    city: 'London'
  }
};

// Without optional chaining — crashes if address is missing
console.log(user.address.city);        // 'London'
console.log(user.profile.avatar);      // TypeError!

// With optional chaining — returns undefined safely
console.log(user.address?.city);       // 'London'
console.log(user.profile?.avatar);     // undefined — no error
console.log(user.profile?.avatar ?? 'default.png'); // 'default.png'

2.10 Object.freeze and Object.assign

// Object.freeze — prevent any modification
const CONFIG = Object.freeze({
  apiUrl: 'https://api.example.com',
  timeout: 5000
});
CONFIG.timeout = 9999; // silently ignored (or error in strict mode)
console.log(CONFIG.timeout); // 5000

// Object.assign — merge into target (mutates first arg)
const target = { a: 1 };
Object.assign(target, { b: 2 }, { c: 3 });
console.log(target); // { a: 1, b: 2, c: 3 }
// Spread is usually preferred — it creates a new object

3. Real World Example

// User settings with defaults
const defaultSettings = Object.freeze({
  theme:        'light',
  language:     'en',
  notifications: true,
  fontSize:     16
});

function loadSettings() {
  const saved = localStorage.getItem('settings');
  return saved ? { ...defaultSettings, ...JSON.parse(saved) } : { ...defaultSettings };
}

function saveSettings(changes) {
  const current = loadSettings();
  const updated = { ...current, ...changes }; // merge without mutation
  localStorage.setItem('settings', JSON.stringify(updated));
  return updated;
}

function applySettings(settings) {
  document.body.classList.toggle('dark', settings.theme === 'dark');
  document.documentElement.style.fontSize = settings.fontSize + 'px';
}

let settings = loadSettings();
applySettings(settings);

document.querySelector('#theme-btn').addEventListener('click', () => {
  const newTheme = settings.theme === 'light' ? 'dark' : 'light';
  settings = saveSettings({ theme: newTheme });
  applySettings(settings);
});

4. Code Example

<input id="name-in"  placeholder="Name">
<input id="email-in" placeholder="Email">
<input id="age-in"   placeholder="Age" type="number">
<button id="save">Save Profile</button>
<pre id="output"></pre>

<script>
  let profile = {};

  document.querySelector('#save').addEventListener('click', () => {
    const name  = document.querySelector('#name-in').value.trim();
    const email = document.querySelector('#email-in').value.trim();
    const age   = Number(document.querySelector('#age-in').value);

    // Build the profile object
    profile = {
      name,
      email,
      age,
      createdAt: new Date().toISOString(),
      meta: {
        version: 1,
        lastUpdated: Date.now()
      }
    };

    // Display it
    document.querySelector('#output').textContent =
      JSON.stringify(profile, null, 2);
  });

  // Destructuring in use
  document.querySelector('#output').addEventListener('click', () => {
    const { name, email, meta: { lastUpdated } } = profile;
    if (name) alert(`${name} — ${email}\nUpdated: ${new Date(lastUpdated).toLocaleString()}`);
  });
</script>

5. Code Breakdown

Shorthand property names

Inside the save handler, { name, email, age } uses shorthand — the variable names match the desired property names, so no repetition of name: name.

Nested object

The meta property contains a nested object. This groups related fields (version, lastUpdated) under one key, keeping the top-level object clean.

Nested destructuring

const { name, email, meta: { lastUpdated } } extracts name and email from the top level and lastUpdated from inside meta — all in one statement.

JSON.stringify with indentation

The second argument (null) is a replacer filter, the third (2) is the number of spaces for indentation — making the output human-readable.

6. Common Mistakes

Mistake 1 — Treating object assignment as a copy

const a = { x: 1, y: 2 };
const b = a;       // same reference!
b.x = 99;
console.log(a.x);  // 99 — a was mutated too!

// Fix — use spread
const c = { ...a };
c.x = 99;
console.log(a.x);  // 1 — safe

Mistake 2 — Accessing deeply nested property without optional chaining

const resp = { data: null };
console.log(resp.data.user.name); // TypeError: Cannot read properties of null

// Fix
console.log(resp.data?.user?.name ?? 'Unknown'); // 'Unknown'

Mistake 3 — Using for...in on arrays

const arr = ['a', 'b', 'c'];
for (const key in arr) {
  console.log(key); // '0', '1', '2' — strings, not numbers!
  // Also iterates inherited prototype properties
}
// Fix: use for...of for arrays, for...in only for plain objects

Mistake 4 — Shallow copy doesn't deep-clone nested objects

const original = { a: 1, nested: { b: 2 } };
const copy = { ...original };
copy.nested.b = 99;
console.log(original.nested.b); // 99 — nested object shared!

// Fix for deep clone (modern browsers):
const deep = structuredClone(original);

7. Best Practices

  1. Use object literals for creating plain data objects.
  2. Prefer dot notation for known keys; use bracket notation for dynamic or special-character keys.
  3. Use destructuring in function parameters to document which properties you use.
  4. Use spread to copy/merge — never mutate objects you didn't create.
  5. Use optional chaining (?.) when accessing properties of potentially null/undefined objects.
  6. Use Object.freeze for constants that should never change (config, default settings).

8. Practice Exercise

  1. Create a book object with title, author, year, pages, and a nested ratings object with average and count. Access and log every value using both dot and bracket notation.
  2. Write a function merge(obj1, obj2) that returns a new merged object. Properties in obj2 should override obj1. Neither argument should be mutated.
  3. Given an array of objects { name, score }, use Object.entries on a scores object to find and log the person with the highest score.

9. Assignment

Build a "Contact Book" application.

  1. Store contacts as objects: { id, name, email, phone, tags: [] }.
  2. Keep contacts in an array. Provide: add contact, delete by id, update a contact's email or phone, and search by name.
  3. Display all contacts in a table. Each row shows all fields plus Edit and Delete buttons.
  4. Use destructuring when reading contact fields in your display function.
  5. Use spread when updating a contact — never mutate the original object directly.

Deliverable: One HTML file.

10. Interview Questions

  1. What is the difference between dot notation and bracket notation?
    Dot notation (obj.key) is shorter and cleaner — use it for known, valid identifier keys. Bracket notation (obj['key'] or obj[variable]) is needed for keys with special characters, spaces, or when the key is stored in a variable.
  2. What is object destructuring?
    A syntax that extracts properties from an object into variables in one statement: const { name, age } = user. It can rename variables, set defaults, and handle nested structures.
  3. How do you copy an object without mutating the original?
    Use the spread operator: const copy = { ...original }. This creates a shallow copy — nested objects are still shared. For deep copying, use structuredClone(original).
  4. What does Object.keys() return?
    An array of the object's own enumerable property names (strings). Paired with Object.values() (values) and Object.entries() ([key, value] pairs), these three methods cover most iteration needs.
  5. What is optional chaining and why is it useful?
    The ?. operator safely accesses nested properties — if any part of the chain is null or undefined, it returns undefined instead of throwing a TypeError. Essential when working with API responses where fields may be missing.

11. Additional Resources

  • MDN — Working with objects
  • MDN — Destructuring assignment
  • MDN — Object.keys() / values() / entries()
  • javascript.info — Objects