SYP Start Your Path

Module 1: Classes & OOP

Classes & Constructors

A class is a blueprint for creating objects that share the same structure and behavior:

class User {
  constructor(name, age) {   // runs automatically when a new object is created
    this.name = name;
    this.age = age;
  }

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

let user1 = new User("Chiru", 25);
user1.greet(); // "Hi, I'm Chiru"

Inheritance

One class can extend another, reusing and building on its behavior — this is encapsulation (bundling data and behavior together) combined with inheritance (sharing that bundle across related classes):

class Admin extends User {
  constructor(name, age, permissions) {
    super(name, age); // calls User's constructor first
    this.permissions = permissions;
  }

  greet() {
    super.greet();      // calls User's greet() first
    console.log("I'm an admin");
  }
}

let admin = new Admin("Ankur", 30, ["manage-users"]);
admin.greet();
// "Hi, I'm Ankur"
// "I'm an admin"

This is also polymorphism in action — Admin and User both have a greet() method, but each runs its own version, and code that expects a User can work with an Admin too.

this, call, apply & bind

Inside a method, this refers to the object the method was called on:

let counter = {
  count: 0,
  increment() {
    this.count++;
  }
};
counter.increment();

call, apply, and bind let you explicitly control what this refers to:

function greet() {
  console.log(`Hi, I'm ${this.name}`);
}
let user1 = { name: "Chiru" };

greet.call(user1);        // runs immediately, this = user1
greet.apply(user1, []);    // like call, but arguments as an array

let boundGreet = greet.bind(user1); // returns a NEW function, permanently locked to user1
boundGreet(); // can be called anytime later, this never changes

bind is especially useful when passing a method as a callback, where it would otherwise lose track of its original this.

Prototypes & the Prototype Chain

Under the hood, JavaScript classes are built on prototypes — every object has a hidden link to another object (its prototype), which it can borrow properties and methods from. When you access user1.greet(), JavaScript looks for greet on user1 itself first, and if not found, walks up the prototype chain until it finds it (or reaches the end and gives up).

class User {
  greet() { console.log("Hi"); }
}
let user1 = new User();

Object.getPrototypeOf(user1) === User.prototype; // true
user1.hasOwnProperty("greet"); // false — greet lives on the prototype, not on user1 itself

You rarely need to work with prototypes directly in modern code — classes give you the same power with much clearer syntax — but understanding they exist under the hood explains a lot of JavaScript's behavior.

Getters & Setters

Let you define properties that run code when read or written, while still looking like plain property access from the outside:

class Circle {
  constructor(radius) {
    this._radius = radius;
  }

  get area() {
    return Math.PI * this._radius ** 2;
  }

  set radius(value) {
    if (value <= 0) throw new Error("Radius must be positive");
    this._radius = value;
  }
}

let c = new Circle(5);
console.log(c.area);  // called like a property, not a method — no ()
c.radius = 10;          // also looks like a plain assignment
console.log(c.area);