DEV Community

Ahmed Said-ahmed
Ahmed Said-ahmed

Posted on

What happens when you create a class instance in Node.js?

Starting from JavaScript ES6 and later, we became able to write classes the way we used to in other programming languages and as a result, we can also create instances the same way using the new keyword. Along with that, we got some of the OOP features like inheritance.

That said, JavaScript still has a long way towards being a fully object-oriented language — we are talking about vanilla JavaScript, so TypeScript is out of the conversation.

Now to understand what happens when you create a new instance. Let’s consider the following example:

class Animal{
  species;
  constructor(){}
}

class Dog extends Animal{
  name;
  constructor(){
    super();
  }
}

let max = new Dog();
Enter fullscreen mode Exit fullscreen mode

Let’s have a close look at the example above and think for a while, what happens when new Dog() gets called?

First, the class attributes get declared then the constructor of the class get called, and store the necessary values in the newly created instance variable max. Then, the function super() starts to go up in the inheritance line towards Animal class and run its constructor, but not the attributes and methods. That is the extends keyword’s job which is making sure that all attributes and methods of a parent class are accessible in the child class.

That was just an overview of how it works. Let’s play around with it to gain a deeper understanding of what happens. For a starter, we will assign the value mammal to species and console.log it in the child class:

class Animal{
  species;
  constructor(){
    this.species = 'mammal';
  }
}

class Dog extends Animal{
  name;
  constructor(){
    super();
    console.log(this.species);
  }
}

let max = new Dog();
Enter fullscreen mode Exit fullscreen mode

If we run the program, you will find that it will print the word ‘mammal’ to the console. Which is what intended. Let’s see what happened:

  • Dog attribute name is declared.
  • Dog constructor gets initiated which calls super() that declares attribute species of the parent class Animal and initiates the parent constructor.
  • The parent constructor assigns the value mammal to species.
  • The Dog constructor continues and console log the value. What would happen if we console log species before calling super:
constructor(){
    console.log(this.species);
    super();
}
Enter fullscreen mode Exit fullscreen mode

When you run the program, you will get a reference error telling you that you need to call the super() method before using this keyword or returning from the derived constructor. That makes sense because super() behaves as if you are initiating an instance but for whatever parent class is there. The super function:

  • Initiates a new parent instance, for example, new Animal().
  • Attach this instance with this keyword that preserves the current instance. Well, let’s get a bit crazier. We will assign a value ‘max’ to the attribute name in Dog class and then console.log it on the Animal class:
class Animal{
  species;
  constructor(){
    this.species = 'mammal';
    console.log("Name", this.name);
  }
}

class Dog extends Animal{
  name = 'max';
  constructor(){
    super();
    console.log("Species", this.species);
  }
}

let max = new Dog();
Enter fullscreen mode Exit fullscreen mode

Before you run the above code, can you guess what the output will be?

The program will not break when you run it. However, although the name attribute is assigned in Dog class, the output will be Name undefined. According to the behavior that we mentioned earlier, that would be considered weird, since we assigned the value on the class declaration which is a new feature in Node that only works on modern browsers only (just in case it broke on your machine, then you’ll know why).

To understand what happened, we need to output this to the console using console.dir():

constructor(){
  this.species = 'mammal';
  console.dir("Object", this); //this will show object details
  console.log("Name", this.name);
}
Enter fullscreen mode Exit fullscreen mode

output:

//output
'Object'
Name undefined
Dog { species: undefined, name: 'max' }
Enter fullscreen mode Exit fullscreen mode

When you run this, you will notice 2 things:

Although you output the object before the name field, first it will show object then the Name field will show up, and lastly, the instance details will show up at the end.
The name is initialized inside this but it’s undefined when you call name.
What we can learn from this is that console.dir waits until the inheritance tree is built then output its fields which means that the object is not completely built at this stage. And we can prove that by seeing this.name returning undefined.

The point here is that you should not expect a child class to pass a value to its parent’s constructor. But, you can use it later in a method or from the new created instance:

let max = new Dog();
max.name;
Enter fullscreen mode Exit fullscreen mode

Node.js’s OOP is very young. It’s still missing many features including interfaces and access modifiers which are pretty important in encapsulation. To explore all Node’s new features, I’ve built an npm package to reach its current limits and this article unraveled one of its weird parts. And I intend to share more whenever I get a chance.

Top comments (0)