Understanding JavaScript Classes: Behind the Scenes

Understanding JavaScript Classes: Behind the Scenes

In JavaScript, classes provide a neat and organized way to define objects and their behaviors. However, before ES6 introduced classes, everything was done using functions and prototypes. Let's dive into how classes work and what happens behind the scenes when we don't use them.

Class Syntax

Consider a simple class User that initializes a user object and has methods to encrypt a password and change the username.

class User {
  constructor(username, email, password) {
    this.username = username;
    this.email = email;
    this.password = password;
  }

  encryptPassword() {
    return `${this.password}abc`;
  }

  changeUsername() {
    return `${this.username.toUpperCase()}`;
  }
}

const Chai = new User("chai", "chai@gmail.com", "123");
console.log(Chai.encryptPassword()); // Output: 123abc
console.log(Chai.changeUsername());  // Output: CHAI

Explanation:

  • When an object is created using new User(...), the constructor method is called automatically, initializing the properties.

  • Methods like encryptPassword and changeUsername are defined inside the class and can be directly called on any instance of the class.

Behind the Scenes: Without Classes

Before classes, functions were used to define objects, and prototypes were used to define methods.

function User(username, email, password) {
  this.username = username;
  this.email = email;
  this.password = password;
}

User.prototype.encryptPassword = function() {
  return `${this.password}abc`;
};

User.prototype.changeUsername = function() {
  return `${this.username.toUpperCase()}`;
};

const tea = new User("tea", "tea@gmail.com", "123");
console.log(tea.encryptPassword()); // Output: 123abc
console.log(tea.changeUsername());  // Output: TEA

Explanation:

  • A function User is used as a constructor function, initializing the object's properties when called with new.

  • Methods like encryptPassword and changeUsername are added to the User.prototype, allowing all instances to share the same methods without duplicating them for each object.

Key Takeaways:

  • Classes provide a cleaner, more intuitive syntax for creating objects and defining methods but are essentially syntactic sugar over the prototype-based inheritance model.

  • Constructor Functions and Prototypes were the way to create objects and methods before classes were introduced.

  • Using classes improves code readability, maintenance, and organization, while prototypes still form the core of JavaScript's inheritance system.

JavaScript Classes and Inheritance Explained

JavaScript classes make object-oriented programming easy by providing a clean, intuitive syntax for creating objects and implementing inheritance. Here's a step-by-step look at how it all works:

class User{
    constructor(username){
        this.username = username;
    }
    logMe(){
        console.log(`username is ${this.username}`);
    }
}

class Teacher extends User{
    constructor(username, email, password){
        super(username);
        this.email = email;
        this.password = password;
    }

    addCourse(){
        console.log(`new course was added by ${this.username}`);
    }
}

const chai = new Teacher("chai", "chai@gmail.com", "123");
chai.addCourse(); //new course was added by chai

const masalaChai = new User("masalaChai");
//now let's check that object of User class have access to method of another class

masalaChai.addCourse(); //outputs: It throws error

masalaChai.logMe(); //outputs: username is masalachai

//Note - child class can inherit properties of its parent class
masalaChai.logMe(); //outputs: username is masalachai

Checking Instance of a Class

We can also check that any particular object is an instance of which class

console.log(chai instanceof Teacher); //outputs: true

console.log(chai instanceof User); //outputs: true
// as User is the parent class of the Teacher class, 
//therefore objects of Teacher class will also be an instance of User class

Static Properties and Methods

Static methods belong to the class itself, not the instances of the class. This means they can't be accessed from objects created from the class. Here's an example:

class User{
    constructor(username){
        this.username = username;
    }
    logMe(){
        console.log(`username is ${this.username}`);
    }

    //now we want as soon as a user is created(object of User class is created),
    // a unique id is attached to it, so we want to create such a method or function
    createId(){
        return `123`;
    }
}

const newUser = new User("harsh");
console.log(newUser.createId()); //outputs: 123

Now sometimes we don't want to give access of this method to those objects which is instantiated from User class, to do so, use "static" keyword in front of that function

class User{
    constructor(username){
        this.username = username;
    }
    logMe(){
        console.log(`username is ${this.username}`);
    }

    //Static method that doesn't belong to the instance
    static createId(){
        return `123`;
    }
}

const newUser = new User("harsh");
console.log(newUser.createId()); //outputs: it will throw an error
console.log(User.createId()); // Outputs: 123

Using the static keyword, you can create methods that are meant to be utility functions rather than instance methods, Also if you extend this class, it won’t provide any access to “createId“ method to the extended class.

class User{
    constructor(username){
        this.username = username;
    }
    logMe(){
        console.log(`username is ${this.username}`);
    }

    //Static method that doesn't belong to the instance
    static createId(){
        return `123`;
    }
}

const newUser = new User("harsh");
console.log(newUser.createId()); //outputs: it will throw an error
console.log(User.createId()); // Outputs: 123

class Teacher extends User {
    constructor(username, email){
        super(username);
        this.email = email;
    }
}

const iphone = new Teacher("iphone", "iphone@gmail.com");
iphone.logMe(); //outputs: username is iphone
// console.log(iphone.createId()); //it will also throw an error