Home Module 12 Object Methods

1. Introduction

An object can store not just data (strings, numbers, booleans) but also functions. A function stored as a property is called a method. Methods let an object carry its own behaviour alongside its data — user.greet(), cart.total(), timer.start(). Inside a method, the keyword this refers to the object the method belongs to.

2. Theory

2.1 Defining methods

// Method shorthand (preferred)
const user = {
  name: 'Alice',
  age:  28,
  greet() {
    console.log(`Hi, I'm ${this.name}`);
  },
  isAdult() {
    return this.age >= 18;
  }
};

user.greet();             // "Hi, I'm Alice"
console.log(user.isAdult()); // true

// Older style — function expression as property
const user2 = {
  name: 'Bob',
  greet: function() {
    console.log(`Hi, I'm ${this.name}`);
  }
};

2.2 The this keyword

Inside a regular function used as a method, this refers to the object before the dot at call time.

const counter = {
  count: 0,
  increment() {
    this.count++;      // 'this' is the counter object
    console.log(this.count);
  },
  reset() {
    this.count = 0;
  }
};

counter.increment(); // 1
counter.increment(); // 2
counter.reset();
counter.increment(); // 1

2.3 Arrow functions and this

Arrow functions do NOT have their own this. They inherit this from the surrounding scope. This is why you should NOT use arrow functions as methods.

const obj = {
  name: 'Widget',

  // Regular method — 'this' works correctly
  goodMethod() {
    console.log(this.name); // 'Widget'
  },

  // Arrow method — 'this' is NOT the object
  badMethod: () => {
    console.log(this.name); // undefined (or window.name in browser)
  },

  // Arrow function INSIDE a regular method — useful for callbacks
  doAsync() {
    setTimeout(() => {
      console.log(this.name); // 'Widget' — inherits 'this' from doAsync
    }, 100);
  }
};

2.4 this in callbacks — the problem and fix

const timer = {
  seconds: 0,

  // Problem: regular function loses 'this' context in setInterval callback
  startBroken() {
    setInterval(function() {
      this.seconds++; // 'this' is undefined (strict) or window (sloppy)
      console.log(this.seconds); // NaN or error
    }, 1000);
  },

  // Fix 1: arrow function (preferred)
  startGood() {
    setInterval(() => {
      this.seconds++;          // 'this' = timer object — correct
      console.log(this.seconds);
    }, 1000);
  },

  // Fix 2: save 'this' in a variable (older pattern)
  startOld() {
    const self = this;
    setInterval(function() {
      self.seconds++;
      console.log(self.seconds);
    }, 1000);
  }
};

2.5 Getters and setters

const person = {
  firstName: 'Alice',
  lastName:  'Smith',

  // Getter — computed property accessed like a regular property
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  },

  // Setter — intercepts assignment
  set fullName(value) {
    const [first, ...rest] = value.split(' ');
    this.firstName = first;
    this.lastName  = rest.join(' ');
  }
};

console.log(person.fullName);     // 'Alice Smith' — no ()
person.fullName = 'Bob Johnson';  // triggers setter
console.log(person.firstName);    // 'Bob'
console.log(person.lastName);     // 'Johnson'

2.6 Factory functions — creating many similar objects

// Factory function — returns a new object each time
function createUser(name, email, role = 'user') {
  return {
    name,
    email,
    role,
    createdAt: new Date().toISOString(),

    greet() {
      return `Hello, ${this.name}!`;
    },

    isAdmin() {
      return this.role === 'admin';
    },

    promote() {
      return createUser(this.name, this.email, 'admin');
    }
  };
}

const alice = createUser('Alice', 'alice@example.com');
const bob   = createUser('Bob',   'bob@example.com', 'admin');

console.log(alice.greet());    // "Hello, Alice!"
console.log(bob.isAdmin());    // true
console.log(alice.isAdmin());  // false

2.7 Object.keys/values/entries as utilities

const inventory = {
  apples:  50,
  bananas: 30,
  oranges: 0,
  grapes:  15
};

// Find out-of-stock items
const outOfStock = Object.keys(inventory).filter(item => inventory[item] === 0);
console.log(outOfStock); // ['oranges']

// Total inventory
const total = Object.values(inventory).reduce((sum, qty) => sum + qty, 0);
console.log(total); // 95

// Format for display
const display = Object.entries(inventory)
  .map(([item, qty]) => `${item}: ${qty}`)
  .join(', ');
console.log(display); // 'apples: 50, bananas: 30, oranges: 0, grapes: 15'

2.8 Object.fromEntries — entries back to object

// Convert array of pairs back to an object
const pairs = [['name', 'Alice'], ['age', 28], ['role', 'admin']];
const obj   = Object.fromEntries(pairs);
console.log(obj); // { name: 'Alice', age: 28, role: 'admin' }

// Useful: transform an object's values
const prices  = { shirt: 29, jeans: 59, jacket: 89 };
const doubled = Object.fromEntries(
  Object.entries(prices).map(([key, val]) => [key, val * 2])
);
console.log(doubled); // { shirt: 58, jeans: 118, jacket: 178 }

// FormData to plain object
const form    = document.querySelector('form');
const data    = Object.fromEntries(new FormData(form));
console.log(data); // { username: '...', email: '...' }

3. Real World Example

// Shopping cart object with methods
function createCart() {
  const items = [];

  return {
    get count() {
      return items.reduce((sum, item) => sum + item.qty, 0);
    },

    get total() {
      return items.reduce((sum, item) => sum + item.price * item.qty, 0);
    },

    add(product, qty = 1) {
      const existing = items.find(i => i.id === product.id);
      if (existing) {
        existing.qty += qty;
      } else {
        items.push({ ...product, qty });
      }
      return this; // allow chaining: cart.add(p1).add(p2)
    },

    remove(productId) {
      const idx = items.findIndex(i => i.id === productId);
      if (idx >= 0) items.splice(idx, 1);
      return this;
    },

    clear() {
      items.length = 0;
      return this;
    },

    summary() {
      return items.map(i => `${i.name} x${i.qty} = $${(i.price * i.qty).toFixed(2)}`);
    }
  };
}

const cart = createCart();
cart.add({ id: 1, name: 'Shirt',  price: 29 })
    .add({ id: 2, name: 'Jeans',  price: 59 })
    .add({ id: 1, name: 'Shirt',  price: 29 }, 2); // add 2 more shirts

console.log(cart.count);     // 4
console.log(cart.total);     // 29*3 + 59 = 146
console.log(cart.summary()); // ['Shirt x3 = $87.00', 'Jeans x1 = $59.00']

4. Code Example

<div id="timer-display">00:00</div>
<button id="start">Start</button>
<button id="pause">Pause</button>
<button id="reset">Reset</button>

<script>
  const stopwatch = {
    seconds:  0,
    intervalId: null,

    get display() {
      const m = String(Math.floor(this.seconds / 60)).padStart(2, '0');
      const s = String(this.seconds % 60).padStart(2, '0');
      return `${m}:${s}`;
    },

    start() {
      if (this.intervalId) return; // already running
      this.intervalId = setInterval(() => {
        this.seconds++;
        document.querySelector('#timer-display').textContent = this.display;
      }, 1000);
    },

    pause() {
      clearInterval(this.intervalId);
      this.intervalId = null;
    },

    reset() {
      this.pause();
      this.seconds = 0;
      document.querySelector('#timer-display').textContent = this.display;
    }
  };

  document.querySelector('#start').addEventListener('click', () => stopwatch.start());
  document.querySelector('#pause').addEventListener('click', () => stopwatch.pause());
  document.querySelector('#reset').addEventListener('click', () => stopwatch.reset());
</script>

5. Code Breakdown

Getter — display

The display getter computes a formatted time string from this.seconds on demand. It is accessed as stopwatch.display (no parentheses), yet it runs fresh every time — combining data and behaviour cleanly.

Arrow function in setInterval

The arrow callback inside setInterval inherits this from start(), which is the stopwatch object. Without the arrow function, this inside the callback would be undefined in strict mode.

Guard clause in start()

if (this.intervalId) return; prevents starting a second timer if start is called while already running — a classic guard clause pattern.

6. Common Mistakes

Mistake 1 — Arrow function as a method

const obj = {
  value: 42,
  getValue: () => this.value // 'this' is NOT obj — it's the outer scope
};
console.log(obj.getValue()); // undefined

// Fix — use method shorthand
const obj2 = {
  value: 42,
  getValue() { return this.value; } // 'this' = obj2
};

Mistake 2 — Losing this when passing a method as a callback

const player = {
  name: 'Alice',
  greet() { console.log(`I am ${this.name}`); }
};

// Bug — 'this' is lost when the method reference is detached
setTimeout(player.greet, 100); // 'I am undefined'

// Fix 1: wrap in arrow function
setTimeout(() => player.greet(), 100); // correct

// Fix 2: bind
setTimeout(player.greet.bind(player), 100); // correct

Mistake 3 — Mutating shared state in object methods

// If multiple objects share the same nested reference, mutations affect all
const template = { data: [], add(x) { this.data.push(x); } };
const a = { ...template };
const b = { ...template };
a.add(1);
console.log(b.data); // [1] — same array reference! Bug.

// Fix — ensure each instance gets its own array (use a factory function)

7. Best Practices

  1. Use method shorthand (greet() { }) for methods — shorter and correctly sets this.
  2. Never use arrow functions as direct methods — they break this.
  3. Use arrow functions inside methods (as callbacks) — they correctly inherit the method's this.
  4. Use getters for computed properties that should look like regular properties to callers.
  5. Use factory functions when you need to create multiple similar objects.
  6. Return this from mutating methods to enable method chaining.

8. Practice Exercise

  1. Create a bankAccount object with properties owner and balance, and methods deposit(amount), withdraw(amount) (reject if insufficient funds), and statement() that logs the current balance. Test all methods.
  2. Create a calculator object with methods add, subtract, multiply, divide, and result. Make each method return this so you can chain: calculator.add(5).multiply(3).subtract(2).result().
  3. Use Object.fromEntries and Object.entries to write a function mapValues(obj, fn) that applies fn to every value and returns a new object.

9. Assignment

Build a "Task Manager" using an object-oriented approach.

  1. Create a createTaskManager() factory function that returns an object with: addTask(title, priority), completeTask(id), deleteTask(id), filterByPriority(level), getStats() (returns totals, completed count, pending count), and a count getter.
  2. Wire up a UI: input for title, select for priority (high/medium/low), list of tasks with complete and delete buttons.
  3. Show stats (total/done/pending) that update automatically after every action.
  4. All task data lives inside the factory — UI only calls methods and reads from the returned object.

Deliverable: One HTML file.

10. Interview Questions

  1. What is a method in JavaScript?
    A function stored as a property of an object. Called with object.method(), it has access to the object's other properties via this.
  2. What does this refer to in a regular method?
    The object the method was called on — specifically, the object before the dot at call time. If the method reference is detached (stored in a variable), this becomes undefined in strict mode.
  3. Why shouldn't you use arrow functions as object methods?
    Arrow functions don't have their own this — they inherit it from the lexical scope where they were defined. When used as methods, this will be the outer scope (often window or undefined), not the object.
  4. What is a getter and when do you use it?
    A getter is a method defined with the get keyword that is accessed as if it were a regular property (no parentheses). Use it for computed values that depend on other properties — like a fullName computed from firstName and lastName.
  5. What is a factory function?
    A regular function that creates and returns a new object. Each call produces an independent instance. It is a simple alternative to classes for creating multiple similar objects with shared methods.

11. Additional Resources

  • MDN — Methods and this
  • MDN — Getter / setter
  • javascript.info — Object methods, "this"
  • MDN — Object.fromEntries()