Day 17: this, Classes & Object-Oriented JS
✅ Today you will learn
- The four rules of this
- call, apply and bind
- Classes: constructor, methods, getters/setters
- static members and private # fields
- Inheritance with extends/super – and when to prefer composition
🏢 In the real world
The cart logic is now needed on the product page, the cart page and the checkout page. Copying functions around leads to three slightly different carts. A Cart class gives one blueprint: every page creates a cart object with the same methods and rules, and its internals stay private so no page can break the totals by accident.
The four rules of this
'use strict';
const shop = {
name: 'Sharma Sweets',
greet() { return `Welcome to ${this.name}`; }, // 1. method call → this = the object before the dot
later() { return [1].map(() => this.name)[0]; }, // 2. arrow → this from the surrounding method
};
console.log(shop.greet());
console.log(shop.later());
const loose = shop.greet; // 3. plain call → this is undefined (strict mode)
try { loose(); } catch (e) { console.log('Lost this:', e.message); }
const bound = shop.greet.bind(shop); // 4. bind/call/apply set this explicitly
console.log(bound());
console.log(shop.greet.call({ name: 'Gupta Bakery' }));| How the function is called | this is… |
|---|---|
| obj.method() | obj |
| plain fn() (strict / modules / classes) | undefined |
| Arrow function | Whatever this was where the arrow was written |
| fn.call(x) / fn.apply(x) / fn.bind(x) | x |
| new Fn() / new Class() | The new object |
| Event listener with a regular function | The element (event.currentTarget) |
Use method shorthand for object/class methods, and arrow functions for callbacks inside them – then this almost always means what you expect.
Classes: blueprints for objects
class Product {
constructor(id, name, price, stock = 0) { // runs on new Product(...)
this.id = id;
this.name = name;
this.price = price;
this.stock = stock;
}
get inStock() { return this.stock > 0; } // getter – used like a property
priceFor(kg) { return Math.round(this.price * kg); }
toString() { return `${this.name} (₹${this.price}/kg)`; }
}
const kaju = new Product(1, 'Kaju Katli', 900, 25);
const kalakand = new Product(5, 'Kalakand', 600);
console.log(String(kaju), kaju.priceFor(1.5), kaju.inStock);
console.log(`${kalakand}`, kalakand.inStock);
console.log(typeof Product, kaju instanceof Product);A real Cart class: static and private fields
class Cart {
#items = new Map(); // private field: id → { product, qty }
static FREE_DELIVERY = 1500;
static DELIVERY_CHARGE = 60;
add(product, qty = 1) {
if (qty <= 0) throw new Error('Quantity must be positive');
const line = this.#items.get(product.id);
this.#items.set(product.id, { product, qty: (line?.qty ?? 0) + qty });
return this; // allows chaining: cart.add(a).add(b)
}
remove(id) { this.#items.delete(id); return this; }
get count() { return [...this.#items.values()].reduce((n, l) => n + l.qty, 0); }
get subtotal() { return [...this.#items.values()].reduce((s, l) => s + l.product.price * l.qty, 0); }
get delivery() { return this.subtotal === 0 || this.subtotal >= Cart.FREE_DELIVERY ? 0 : Cart.DELIVERY_CHARGE; }
get total() { return this.subtotal + this.delivery; }
lines() { return [...this.#items.values()].map(l => `${l.product.name} × ${l.qty} = ₹${l.product.price * l.qty}`); }
}
const cart = new Cart();
cart.add({ id: 1, name: 'Kaju Katli', price: 900 }, 1)
.add({ id: 4, name: 'Besan Ladoo', price: 500 })
.add({ id: 1, name: 'Kaju Katli', price: 900 }, 0.5);
console.log(cart.lines().join('\n'));
console.log(`Items ${cart.count} · subtotal ₹${cart.subtotal} · delivery ₹${cart.delivery} · total ₹${cart.total}`);
console.log(cart.items, Object.keys(cart)); // #items is truly private
try { cart.add({ id: 9, name: 'X', price: 1 }, 0); } catch (e) { console.log('Error:', e.message); }| Feature | Syntax | Why |
|---|---|---|
| Private field | #items = new Map() | Cannot be read or changed from outside – protects totals |
| Getter | get total() { … } | Always calculated, never stale |
| Static | static FREE_DELIVERY = 1500 | Shared setting on the class, not on each cart |
| Return this | add() { …; return this; } | Chaining: cart.add(a).add(b) |
| throw | throw new Error(…) | Refuse invalid input loudly (Day 18) |
Inheritance with extends and super
class Product {
constructor(name, price) { this.name = name; this.price = price; }
label() { return `${this.name} – ₹${this.price}`; }
}
class GiftBox extends Product {
constructor(name, price, contents) {
super(name, price); // call the parent constructor FIRST
this.contents = contents;
}
label() { // override, but reuse the parent version
return `${super.label()} 🎁 (${this.contents.join(', ')})`;
}
}
const box = new GiftBox('Assorted Box', 720, ['Kaju Katli', 'Ladoo', 'Soan Papdi']);
console.log(box.label());
console.log(box instanceof GiftBox, box instanceof Product);Deep inheritance
- Product → Sweet → MilkSweet → BengaliMilkSweet
- A change at the top breaks many children
- Hard to model “gift box that is also sugar-free”
Composition ⭐
- A product HAS tags, a discount, a packaging
- Combine small objects/functions
- Flexible – the style React encourages
Real task: using the Cart class in the UI
<div id="menu"></div>
<pre id="summary" style="background:#fefce8;padding:10px;border-radius:8px"></pre>
<script>
class Cart {
#items = new Map(); // private field: id → { product, qty }
static FREE_DELIVERY = 1500;
static DELIVERY_CHARGE = 60;
add(product, qty = 1) {
if (qty <= 0) throw new Error('Quantity must be positive');
const line = this.#items.get(product.id);
this.#items.set(product.id, { product, qty: (line?.qty ?? 0) + qty });
return this; // allows chaining: cart.add(a).add(b)
}
remove(id) { this.#items.delete(id); return this; }
get count() { return [...this.#items.values()].reduce((n, l) => n + l.qty, 0); }
get subtotal() { return [...this.#items.values()].reduce((s, l) => s + l.product.price * l.qty, 0); }
get delivery() { return this.subtotal === 0 || this.subtotal >= Cart.FREE_DELIVERY ? 0 : Cart.DELIVERY_CHARGE; }
get total() { return this.subtotal + this.delivery; }
lines() { return [...this.#items.values()].map(l => `${l.product.name} × ${l.qty} = ₹${l.product.price * l.qty}`); }
}
const products = [
{ id: 1, name: 'Kaju Katli', price: 900 },
{ id: 2, name: 'Malai Ghewar', price: 650 },
{ id: 4, name: 'Besan Ladoo', price: 500 },
];
const cart = new Cart();
const menu = document.querySelector('#menu');
for (const p of products) {
const btn = document.createElement('button');
btn.textContent = `+ ${p.name} (₹${p.price})`;
btn.addEventListener('click', () => { cart.add(p, 0.5); show(); });
menu.append(btn, ' ');
}
function show() {
document.querySelector('#summary').textContent = cart.count
? cart.lines().join('\n') + `\n\nSubtotal ₹${cart.subtotal}\nDelivery ₹${cart.delivery}\nTotal ₹${cart.total}`
: 'Cart is empty – add ½ kg of anything';
}
show();
</script>🏅 Job skills unlocked today
Practice tasks
🏋️ Practice – do it yourself
Show solution
class BankAccount {
#balance = 0;
constructor(owner) { this.owner = owner; }
deposit(amount) { if (amount <= 0) throw new Error('Invalid amount'); this.#balance += amount; return this; }
withdraw(amount) {
if (amount > this.#balance) return `Declined: balance ₹${this.#balance}`;
this.#balance -= amount;
return `Withdrew ₹${amount}`;
}
get balance() { return this.#balance; }
}
const acc = new BankAccount('Riya').deposit(5000);
console.log(acc.withdraw(1200), '|', acc.withdraw(9000), '|', acc.balance);button.addEventListener("click", cart.clear) – inside clear(), this is not the cart.Quick quiz
🧠 Quick Quiz – test yourself
Interview corner
💼 Interview Corner
How does this work in JavaScript?
this is decided by how a function is called: method call → the object; plain call → undefined in strict mode; new → the new instance; call/apply/bind → the given value; arrow functions do not have their own this and use the enclosing one.
Are JavaScript classes real classes?
They are syntax over prototypes: methods live on Class.prototype and objects link to it. Classes add conveniences like private # fields, static blocks and stricter rules (always strict mode, must use new).
Summary
- this depends on the call: method, plain, new, bind, arrow.
- class + constructor + methods + getters model real things.
- #private fields protect data; static holds shared settings.
- extends/super for true “is-a”; prefer composition otherwise.
Day 18: errors and debugging – try…catch, custom errors and finding bugs with DevTools breakpoints.
