const - JS | lectureJavaScript OOP

Constructor Functions and the new Operator

JavaScript OOP

In this lecture, let's finally implement object-oriented programming 🎉 starting with constructor functions. So, let's get started!

We actually kind of used object-oriented programming before but in a very limited way. Because we had no way of programmatically creating objects, hence, all we did was to use some simple object literals. But now, with constructor functions, everything changes.

So, we can use constructor functions to build an object using a function. And a constructor function is just a completely normal function. The only difference between a regular function and a constructor function is that we use the new operator to call a constructor function. Enough talk; let's see it in action 🔥.

Let's say we want to create a constructor function for a person object.

index.js
const Person = function () {};

Before we move on, there are three things I want to point out.

  1. When creating a constructor function, we always use a capital letter at the beginning of the function name. This is just a convention. It's not a requirement. Other built-in constructor functions like Array, or Map, follow this convention as well.
  2. The above function is written using a function expression, but a function declaration would work as well.
  3. An arrow function would not work because arrow functions do not have their own this keyword.

Remember that our constructor function is going to produce an object and, in this case, a person object. So, we need to add some properties to this object. So, let's says our person object needs to have a firstName, a lastName, a birthYear, and a profession.

index.js
const Person = function (firstName, lastName, birthYear, profession) {};

Let's now call this constructor function! Don't forget to use the new operator 😉

index.js
const Person = function (firstName, lastName, birthYear, profession) {
  // Do something...
};

new Person('Evan', 'Guerrero', 1992, 'Developer');

The new operator is actually very special. And it does more than just calling the function. So let's see what happens when we call a constructor function using the new operator 🕵️‍♂️.

Behind the scenes, the new operator does four things:

  1. It creates an empty object {}.
  2. The function is then called, and in this function call, the this keyword is set to point to the newly created empty object (this = {}).
  3. The newly created object is linked to a prototype.
  4. The newly created object is automatically returned from the function (==> {}). At this point, the object no longer needs to be empty.

So, let's get into our constructor function and log the this keyword.

index.js
const Person = function (firstName, lastName, birthYear, profession) {
  console.log(this);};

new Person('Evan', 'Guerrero', 1992, 'Developer');

From the steps above, we know that the this keyword is going to point to the newly created empty object.

The this keyword points to the newly created empty object

Let's now use this knowledge to our advantage. We already know that at the end of the constructor function, the this will be returned. Hence, whatever we add to that empty object, will then be returned from the function. And that returned object will be the object we are going to build in our constructor function. So, all we need to do is to add some properties to the this keyword.

index.js
const Person = function (firstName, lastName, birthYear, profession) {
  /**
   * The property names don't have to be the same as the parameter names.
   * We can use any name we want. It's just a convention to use the same name.
   */

  this.firstName = firstName;  this.lastName = lastName;  this.birthYear = birthYear;  this.profession = profession;};

const evan = new Person('Evan', 'Guerrero', 1992, 'Developer');
console.log(evan);

The this keyword with properties set in constructor function

Great! We now have our person object with all the properties set, and each property is set to the value that we passed into the constructor function. Let's recap what we just did.

  1. We created a constructor function for a person object.
  2. We called the constructor function using the new keyword.
  3. A new empty object is was then created right away.
  4. The constructor function is then called, and inside our constructor function, the this keyword was set newly created empty object.
  5. In our constructor function, we added some properties to the this keyword. Hence, at the end of the function, the this keyword now has four new properties.
  6. Finally, the newly created object was returned from the constructor function, which is the result of our above function call (evan).

We can now use this constructor function to create as many person objects as we want. Let's actually create a few more person objects.

index.js
// The blueprint
const Person = function (firstName, lastName, birthYear, profession) {
  //...
};

// The actual objects
const alfred = new Person('Alfred', 'Medina', 1990, 'Designer');
const rodney = new Person('Rodney', 'Dennis', 1994, 'Architect');

console.log(alfred);
console.log(rodney);

Creating multiple person objects using the same constructor function

And indeed, we now have each object on its own, created programmatically using the same constructor function. Now, remember from one of the previous lectures that in classical OOP, an object created from a class is called an instance. In our scenario, we didn't technically create a class here because, as we discussed before, JavaScript doesn't really have classes in the sense of traditional OOP.

However, we did create an object from a constructor function (actually three). And constructor functions have been used since the beginning of JavaScript to kind of simulate classes. Hence we can still say that evan for example, is an instance of Person.

There is actually an operator called instanceof that we can use to check if an object is an instance of a constructor function. Let's see it in action.

index.js
console.log(evan instanceof Person); // true
console.log(alfred instanceof Person); // true

Since we are currently talking about instances, we can also say that the properties we define in our constructor function are called instance properties. And that's because each instance of the constructor function will have it's own copy of those properties.

index.js
const Person = function (firstName, lastName, birthYear, profession) {
  // Instance properties
  this.firstName = firstName;
  //...
};

Next, how do we add methods to our constructor function? Well, just like we added properties, we can, of course, also add methods. Let's add a method called calcAge to our constructor function.

index.js
const Person = function (firstName, lastName, birthYear, profession) {
  // Instance properties
  this.firstName = firstName;
  //...

  this.calcAge = function () {    console.log(2024 - this.birthYear);  };};

This would work just fine here, but this is a really bad practice! Never do this! Never add methods to the constructor function. Why? Imagine we need to create thousands or tens of thousands of person objects. This means that each of these objects will carry around a copy of the calcAge method. And that's a lot of unnecessary memory usage, and also, it's bad for performance. To solve this problem, we are going to use prototypes and prototype inheritance.

To finish this lecture, just note that function constructors are not really a feature of the JavaScript language. Instead, they are simply a pattern that has been developed by other developers, and now everyone simple uses it 😅. And this now includes you as well 😂.