×

In-depth details of Class in JavaScript

A class in JavaScript is a blueprint for creating objects. It allows you to define reusable object structures with properties and methods, making object creation and management more structured and efficient.

JavaScript classes are a blueprint for creating objects. They introduce object-oriented programming (OOP) concepts like encapsulation, inheritance, and abstraction.

Before ES6, JavaScript used constructor functions and prototypes to create and manage objects. With ES6, class syntax was introduced to provide a cleaner, more intuitive way to define object templates.

Why Do We Need Classes If We Already Have Objects?

JavaScript allows creating objects without classes using object literals, but classes provide advantages in scalability, maintainability, and reusability. Let's explore:

class Person { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log(`Hello, my name is ${this.name}`); } } const person = new Person("Charlie", 35); person.greet(); // Hello, my name is Charlie
  1. Cleaner & More Readable compared to functions with prototypes.
  2. Encapsulation: Methods and properties are inside the class.
  3. Inheritance: Easily extend functionality using extends.

Advantages of Classes Over Constructor function & Plain Objects

Read more about Constructor function here

FeatureObject LiteralsConstructor FunctionsClasses (ES6)
Code ReusabilityNo reusabilityReusable with newBest for reuse
EncapsulationHard to group methods/dataUses prototype for methodsMethods inside class
InheritanceNot possiblePossible but complexEasy with extends
ReadabilitySimple for small casesVerbose with prototypesClean and structured

When to Use Classes?

NOTE: If you're just defining a single, simple object, object literals are fine. But for structured, scalable code, classes are the best approach.


Why Don't We Need prototype in ES6 Classes?

With ES6 classes, methods are automatically added to the prototype, so we don't need to manually define them. Must read about __proto__, [[Prototype]], .prototype

Key Takeaway:

  1. In Constructor Functions, we manually define methods on prototype to avoid duplication.
  2. In ES6 Classes, methods are automatically added to prototype, making code cleaner.

Does the greet() Method in a Class Consume Memory Before Calling It?

Yes, but in an efficient way. In JavaScript classes, methods like greet() are stored in the prototype of the class only once and are shared among all instances.

Even though greet() is not executed until you call it, it still exists in memory as part of the class's prototype. However, since it's only stored once (not duplicated in each object), it is memory efficient compared to defining it inside the constructor.

Comparing Memory Efficiency: Class vs. Constructor Function

1. Constructor Function Without Prototype (Memory Waste)

function Person(name) { this.name = name; this.greet = function() { // Each instance has its own copy of greet() console.log(`Hello, my name is ${this.name}`); }; } const person1 = new Person("Alice"); const person2 = new Person("Bob"); console.log(person1.greet === person2.greet); // false (Different function instances)

2. Constructor Function With Prototype (Efficient)

Read more about Constructor function here

function Person(name) { this.name = name; } Person.prototype.greet = function() { // Stored once in prototype console.log(`Hello, my name is ${this.name}`); }; const person1 = new Person("Alice"); const person2 = new Person("Bob"); console.log(person1.greet === person2.greet); // true (Same function reference)

Read more about __proto__, [[Prototype]], .prototype

3. ES6 Class (Automatically Optimized)

class Person { constructor(name) { this.name = name; } greet() { // Automatically added to prototype console.log(`Hello, my name is ${this.name}`); } } const person1 = new Person("Alice"); const person2 = new Person("Bob"); console.log(person1.greet === person2.greet); // true (Same function reference)

Key Takeaways

  1. Class methods (like greet()) are memory efficient because they are stored once in the prototype and shared across all instances.
  2. Memory is not wasted, even if you create 1000 objects—the greet() function exists only once in memory.
  3. Method execution (calling greet()) happens only when needed, but the function itself is already available in the prototype.

So yes, class methods are memory efficient compared to defining methods inside the constructor.


NOTE: In ES6 classes, methods are automatically added to the prototype of the class.

🔹 How Prototype Methods Work in Classes

In ES6 classes, all methods inside the class body are added to ClassName.prototype.

Example:

class Person { constructor(name) { this.name = name; } // This is a prototype method greet() { return `Hello, my name is ${this.name}`; } } const alice = new Person("Alice"); const bob = new Person("Bob"); console.log(alice.greet()); // "Hello, my name is Alice" console.log(bob.greet()); // "Hello, my name is Bob" // Checking the prototype console.log(alice.__proto__ === Person.prototype); // true console.log(alice.greet === bob.greet); // true (Same function from prototype)

All instances share the same greet() method because it is stored in Person.prototype.

🔹 Where Is the greet() Method Stored?

Even though it looks like greet() is inside each instance, it's actually in Person.prototype:

console.log(Person.prototype); // { constructor: ƒ Person(), greet: ƒ greet() } console.log(alice.hasOwnProperty("greet")); // false (greet is not in alice itself, it's in the prototype) console.log(Object.getPrototypeOf(alice) === Person.prototype); // true

This is the same behavior as manually assigning methods to Person.prototype in constructor functions.

Can We Add Methods to Person.prototype Manually?

Yes! Even after defining a class, you can manually add prototype methods.

Person.prototype.sayBye = function () { return `Goodbye from ${this.name}`; }; console.log(alice.sayBye()); // "Goodbye from Alice" console.log(bob.sayBye()); // "Goodbye from Bob"

This works because ES6 classes still use prototypes under the hood.

Even though ES6 class methods look like instance methods, they are actually prototype methods by default.

When Is a Method an "Instance Method" in classes?

If you define a method inside the constructor, then it becomes an instance method (separate for each object):

class Person { constructor(name) { this.name = name; this.greet = function() { // Instance method (not on prototype) return `Hello, ${this.name}`; }; } } const alice = new Person("Alice"); const bob = new Person("Bob"); console.log(alice.greet === bob.greet); // false (Different function instances) console.log(alice.hasOwnProperty("greet")); // true (Stored on each instance)

Inheritance

class Animal { constructor(name) { this.name = name; } speak() { console.log(`${this.name} makes a noise.`); } } class Dog extends Animal { // Inherits everything from Animal bark() { console.log(`${this.name} barks.`); } } const dog = new Dog("Scooby"); dog.speak(); // Scooby makes a noise.

Method overriding

class Animal { constructor(name) { this.name = name; } speak() { console.log(`${this.name} makes a noise.`); } } class Dog extends Animal { // Inherits everything from Animal speak() { console.log(`${this.name} barks.`); } } const dog = new Dog("Scooby"); dog.speak(); // Scooby barks.

NOTE: If you define a private method with the same name in the child class, it’s not overriding — it’s a completely separate method.

What is inherited by a subclass (extends)

All public properties and methods of the parent class All protected fields (by convention, prefixed with _) You can override or extend them in the child class

What is not inherited

  1. Private Fields (#field) Truly private fields declared with # are not accessible or inherited by subclasses They are scoped only to the class they are defined in

super Keyword

super is used to access and call functions or constructors on an object's parent class.

  1. super() inside a constructor Used to call the parent class's constructor. It must be called before using this in a subclass constructor.
class Vehicle { constructor(brand) { this.brand = brand; } describe() { return `This is a ${this.brand}.`; } } class Car extends Vehicle { constructor(brand, model) { super(brand); // Calls Vehicle constructor this.model = model; } describe() { return `${super.describe()} Model: ${this.model}.`; } } const myCar = new Car("Tesla", "Model S"); console.log(myCar.describe()); // This is a Tesla. Model: Model S.

✅ Without super(brand), the subclass can't initialize this.name. ❗ If you skip super() in a subclass constructor, you’ll get Error.

  1. super.method() inside a method Used to call a method from the parent class.
class Animal { speak() { console.log("Animal speaks"); } } class Dog extends Animal { speak() { super.speak(); // calls Animal's speak() console.log("Dog barks"); } }

Useful when you want to want to add extra logic in child method.

NOTE: JavaScript does not support multiple inheritance directly through the extends keyword. However, multiple inheritance can be simulated using mixins or composition.


2. Public, Private, and Protected Fields

2.1 Public Fields (Default)

All properties and methods are public by default.

class Car { constructor(brand) { this.brand = brand; // Public property } start() {// Public method console.log(`${this.brand} is starting...`); } } const car1 = new Car("Tesla"); console.log(car1.brand); // Tesla (Accessible) car1.start(); // Tesla is starting...

2.2 Private Fields (#)

Private fields cannot be accessed outside the class.

class BankAccount { #balance = 0; // Private property constructor(owner) { this.owner = owner; } deposit(amount) { this.#balance += amount; console.log(`Deposited: $${amount}`); } getBalance() { return this.#balance; } } const account = new BankAccount("Alice"); account.deposit(100); console.log(account.getBalance()); // 100 console.log(account.#balance); // Error: Private field

2.2.1. Private Methods (#method())

Private methods cannot be accessed outside the class.

class Logger { logMessage(message) { this.#formatMessage(message); } #formatMessage(message) { // Private method console.log(message); } } const logger = new Logger(); logger.logMessage("System updated."); // System updated. logger.#formatMessage("Test"); // Error

Key Points:

  1. Private fields start with # and cannot be accessed outside the class.
  2. They cannot be modified directly (account.#balance = 500 → Error).
  3. Use them when you want to hide internal data.
  4. Private fields are NOT accessible in subclasses.

Why Use Private Methods?

2.3 Protected Fields (_) (Convention Only)

JavaScript doesn't have true protected fields, but _ is a naming convention to indicate internal use.

class Employee { constructor(name, salary) { this.name = name; this._salary = salary; // Convention: Internal use } getSalary() { return this._salary; } } const emp = new Employee("Bob", 5000); console.log(emp._salary); // Works, but should be avoided

NOTE: _salary is not truly private, just a hint that it's for internal use.

A protected field is:

  1. Accessible inside the class
  2. Accessible inside subclasses (inherited classes)
  3. Not meant to be accessed from outside the class (but technically still possible)

3. Getters & Setters

Used to control access to properties while keeping them private. getters and setters can be used for both private and public fields. However, they are most useful for encapsulating private fields to prevent direct access and modification.

class Product { #price; constructor(name, price) { this.name = name; this.#price = price; } get price() { return `${this.#price}`; } set price(newPrice) { if (newPrice < 0) { console.log("Price cannot be negative!"); } else { this.#price = newPrice; } } } const item = new Product("Laptop", 1200); console.log(item.price); // ₹ 1200 (Getter) item.price = -500; // Price cannot be negative!

Why Use Getters & Setters?


4. Static Methods and Properties

Static methods & properties belong to the class itself, not instances.

class MathHelper { static PI = 3.14159; static square(num) { return num * num; } } console.log(MathHelper.PI); // 3.14159 console.log(MathHelper.square(4)); // 16 const helper = new MathHelper(); console.log(helper.PI); // Undefined console.log(helper.square(4)); // TypeError: helper.square is not a function

Inheritance and Static Members:

Static members are inherited by subclasses and can be called on them directly:

class MathHelper { static PI = 3.14159; //static property static square(num) { //static method return num * num; } } // Inheriting from MathHelper class AdvancedMathHelper extends MathHelper { static cube(num) { //static method return num * num * num; } static areaOfCircle(radius) { // Using the inherited static property PI //Inside a static method, this refers to the class itself return this.PI * this.square(radius); } } console.log(AdvancedMathHelper.square(4)); // 16 (inherited static method) console.log(AdvancedMathHelper.cube(3)); // 27 (own static method) console.log(AdvancedMathHelper.areaOfCircle(5)); // 78.53975 (uses inherited PI and square)

NOTE: Static members are inherited and can be accessed using this or the class name inside the subclass.

Why Use Static Methods?

NOTE: Static methods do not have access to instance properties or methods. Attempting to reference this within a static method refers to the class itself, not an instance.

What if areaOfCircle is not static

class MathHelper { static PI = 3.14159; static square(num) { return num * num; } } class AdvancedMathHelper extends MathHelper { areaOfCircle(radius) { // 'this.constructor' refers to the class (AdvancedMathHelper) return this.constructor.PI * this.constructor.square(radius); } } const helper = new AdvancedMathHelper(); console.log(helper.areaOfCircle(5)); // Output: 78.53975

In an Instance Method: this refers to the instance. this.constructor refers to the class.

In a Static Method: this already refers to the class itself. So this.constructor refers to the constructor of the class, which is usually Function, not useful here. Hence the below will not work in static areaOfCircle() method.

// Wrong — this.constructor is not what you want here return this.constructor.PI * this.constructor.square(radius); // Doesn't work

Summary

JavaScript classes provide a structured and efficient way to create objects using a blueprint pattern. Introduced in ES6, they offer a cleaner syntax compared to traditional constructor functions while supporting core object-oriented programming principles like encapsulation, inheritance, and abstraction. Classes contain constructors for initializing objects, methods that are automatically assigned to the prototype for memory efficiency, and support inheritance through the extends keyword with super() for accessing parent class members. They also include static properties/methods for class-level operations and multiple access modifiers - public by default, truly private fields using # prefix for encapsulation, and protected fields (conventionally marked with _) for internal use. Additional features like getters/setters allow controlled property access, while private methods enable implementation hiding. Under the hood, classes still use JavaScript's prototypal inheritance, but provide a more intuitive and maintainable syntax for object creation and organization, especially beneficial for large-scale applications requiring reusable components with shared behavior. The memory-efficient prototype system ensures method sharing across instances, and the class syntax makes inheritance hierarchies clearer than manual prototype manipulation.