const - JS | lectureJavaScript OOP

ES6 Classes

JavaScript OOP

In the previous lectures, we learned how to implement prototypal inheritance with constructor functions, and then manually setting methods on the constructor function's prototype property. But now, it's time to turn our attention to ES6 classes, which allows us to do the same thing, but using a nicer, and more modern syntax.

As mentioned earlier in the course, classes in JavaScript do not work like traditional classes in other languages like Java or C++. Instead, classes in JavaScript are just syntactic sugar over what we learned in the previous lectures. They still implement prototypal inheritance behind the scenes but with a syntax that is more familiar to developers coming from other languages.

Let's now implement Person again, but this time using ES6 classes.

index.js
class Person {
  // Do something
}

This is the class declaration syntax. And just like functions, we also have a class expression syntax.

index.js
const Person = class {
  // Do something
};

This works because classes are just a special type of function. So although we use the class keyword, behind the scenes, classes are still functions. Hence we have class expressions and class declarations. And I personally prefer the class declaration syntax.

Now that we have our class syntax, the first thing we need to do in our class is to add a constructor method.

index.js
class Person {
  constructor() {    // Do something    // ...  }}

The constructor method actually works in a pretty similar way as a constructor function. But in this case, it is a method of the class, and it needs to be called constructor. And just like in constructor functions, we pass in arguments basically for properties that we want the object to have.

index.js
class Person {
  constructor(firstName, lastName, birthYear, profession) {    // Do something
    // ...
  }
}

The act of creating a new object also works in the same way as before when using the new operator. Hence, whenever we create a new object using the new operator, the constructor method is automatically called.

Let's try that!

index.js
const william = new Person('William', 'Park', 1992, 'Designer');

You can see that when instantiating a new object, nothing changes. Everything looks the same as before. Therefore, just like before, the this keyword inside the constructor method will point to the newly created empty object. Hence, like before, we can now add properties to the object using the this keyword.

index.js
class Person {
  constructor(firstName, lastName, birthYear, profession) {
    this.firstName = firstName;    this.lastName = lastName;    this.birthYear = birthYear;    this.profession = profession;  }
}

Let's now have a look at the william object.

index.js
console.log(william);

william object

Nothing new here. It looks just like before.

So, in the constructor method, we basically have the properties that will be stored in the new object that we want to create. And so now let's add some methods to our class.

To add a method to a class, we simply add a method to the class body, just like we would do with an object.

index.js
class Person {
  constructor(firstName, lastName, birthYear, profession) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.birthYear = birthYear;
    this.profession = profession;
  }

  calculateAge() {    console.log(2024 - this.birthYear);  }}

Now what's important to understand here is that all the methods that we write in the class body i.e., outside of the constructor method, will be on the prototype of the objects, and not on the objects themselves.

class methods

And so now we can call the calculateAge method on the william object.

index.js
william.calculateAge(); // ==> 32

We can also check just to be sure that the __proto__ property of the william object is the Person.prototype object.

index.js
console.log(william.__proto__ === Person.prototype); // ==> true

The Person class just acts like any other function constructor. The only difference is that the class syntax a nicer. With the class syntax, we don't have to manually mess with the prototype property. All we have to do is to write the methods in the class body, and they will automatically be added to the prototype property of the class.

We can actually take this demonstration even further by adding a method manually to the prototype property of the Person class. And that's going to work just fine 😉.

index.js
Person.prototype.present = function () {
  console.log(`Hi, I'm ${this.firstName} ${this.lastName}`);
};

Let's now call the present method on the william object.

index.js
william.present(); // ==> Hi, I'm William Park

This proves that the class really just hides the true nature of prototypal inheritance in JavaScript. So, of course, we could now do the same thing inside the class body. Remember 💡 to comment out the present method that we just added to the prototype property of the Person class.

index.js
class Person {
  constructor(firstName, lastName, birthYear, profession) {
    //...
  }

  calculateAge() {
    console.log(2024 - this.birthYear);
  }

  present() {    console.log(`Hi, I'm ${this.firstName} ${this.lastName}`);  }}

Also, note that there are no commas between the methods in the class body. Now, if you try to call the present method on the william object, it should work just fine.

To finish this lecture, there are a couple of things we need to keep in mind when using classes in JavaScript.

  1. Classes are not hoisted, even if we use the class declaration syntax. So, remember that function declarations are hoisted, which means that we can use them before they are declared in the code. But with classes, that doesn't work.
  2. Just like functions, classes are first-class citizens. So, we can pass them into functions and return them from functions.
  3. The body of a class is always executed in strict mode.