What actually happens under the hood when we create a class in JS ?

JavaScript classes introduced in ECMAScript 2015 are syntactical sugar over JavaScript's existing prototype-based inheritance. So JS is looking more like other OOP languages, but what actually happens under hood? In this article I would like to dig deep into JavaScript and try to explain with code  examples and console logs.

Let's declare a class.

class Car { 
    constructor(model, color) {
        this.model = model
        this.color = color
    }

    changeModel(newModel) {
        this.model = newModel
    }

    changeColor(newColor) {
        this.color = newColor
    }
}

const car = new Car("BMW", "black")
car.changeModel("Mercedes")

This is very familiar if you are have basic knowledge of OOP. In order to understand how this works let's actually do the same thing we did above using the "old way". 

// create an object with dot notation 

const car = {}
car.model = "BMW"
car.color = "black"
car.changeColor = function (newColor) {
    car.color = newColor
}
car.changeModel = function (newModel) {
    car.model = newModel
}

As we can see it would be extremely inefficient to organize our code this manner. Since every time we need create an object we have to manually write all properties and assign values to it. Imagine the hustle. So what is the second "best" thing we can do about? 

function Car(model, color) {
    const newCar = {}
    newCar.model = model
    newCar.color = color

    newCar.changeColor = function(newColor) {
        newCar.color = newColor
    }
    newCar.changeModel = function(newModel) {
        newCar.model = newModel
    }
    return newCar
}

const bmw = Car("BMW", "black")
bmw.changeColor("white")

It does look much better, our code organized. If you understand up to this point, congrats you are almost there. From now all the steps gonna just making our last code more efficient. As you can see it looks good, however the there is slight problem with that. Every time we create an object we saving the same functionality in our memory. Also if we need to add new feature we have to add it to each object manually. Imagine we have 3000 objects. So what can we do about. It would be better if you could have a "special" place for functions and every time we create an object it would automatically inherit those functionalities.

// create an object with Object.create

Let create an object where we can store object functionalities

const carRelatedFunctions = {
    changeColor : function(newModel) {
        this.newModel = newModel
    },
    change_color: function (new_color) {
        this.newColor = newColor
    }
}

function Car(model, color) {
    const newCar = Object.create(carRelatedFunctions)
    newCar.newColor = color
    newCar.newModel = model

    return newCar
}

const bmw = Car("BMW", "black")
const mercedes = Car("Mercedes", 'white')
bmw.changeColor("white")
mercedes.changeColor("black")

// console.log bwm
=> {newColor: "white", newModel: "BMW"}

// console.log mercedes 
=> {newColor: "black", newModel: "Mercedes"}

As we can see it works, now if need to add new functionality we just add it inside carRelatedFunctions. But wait a minute, if we look at Car object we can't find changeColor functionality so how does newly created object know where to look for. When we created an object with Object.create(carRelatedFunctions) it actually made a link between Car object and carRelatedFunctions through __proto__ property.

Let's see it in console:

=> {newColor: "black", newModel: "Mercedes"}
newColor: "black"
newModel: "Mercedes"
__proto__:
changeColor: ƒ (newColor)
changeModel: ƒ (newModel)
__proto__: Object

 So far everything looks good, however if we think of optimization, is there any way can automate new operations?

// create an object with new keyword. 

function Car(model, color) {
    this.model = model
    this.color = color
    console.log(this)

}

const bmw = new Car("BMW", "black");

That code is a bit short, what is happening? So when we create an object with new keyword it creates empty object and assigns implicit variable this to it and returns that new created object.

Let's console.log mercedes. 

const mercedes = new Car("Mercedes", "white")
=> Car{color: "white", model: "Mercedes"}

What about functions? All functions in their object format automatically have property called prototype. So we will put all functions there. 

Car.prototype.changeColor = function(newColor) {
    this.color = newColor
}

Car.prototype.changeModel = function(newModel) {
    this.model = newModel
}

bmw.changeColor("red")

=> Car {model: "BMW", color: "red"}
color: "red"
model: "BMW"
__proto__:
changeColor: ƒ (newColor)
changeModel: ƒ (newModel)
constructor: ƒ Car(model, color)
__proto__: Object

As you can see it does the same thing. Before we linked our functionalities manually Object.create(carRelatedFunctions) where the link was established. With new keyword Object returned from function call new Car() automatically binds functionalities through its __proto__ to the Car function's object property called prototype.

Hope you made till here and at last the grande finale, what does class keyword do, it just syntactical sugar of same code we went above with new keyword. So now we have a chance to do everything in one place.

class Car { 
    
    constructor(model, color) {
        this.model = model
        this.color = color
    }

    // same as 
    function Car(model, color) {
        this.model = model
        this.color = color
    }

    changeModel(newModel) {
        this.model = newModel
    }

    changeColor(newColor) {
        this.color = newColor
    }

    // same as  
    Car.prototype.changeColor = function(newColor) {
        this.color = newColor
    }

    Car.prototype.changeModel = function(newModel) {
        this.model = newModel
    }
}

That's it, hope you have better understanding of how classes in Javascript work.