loading...
Cover image for An Easy Guide To Understanding Classes In JavaScript

An Easy Guide To Understanding Classes In JavaScript

lawrence_eagles profile image Lawrence Eagles Updated on ・10 min read

Table of Contents

  1. An Introduction To Classes
  2. Classes In Detail
  3. Subclasses In Detail
  4. Classes In Action
  5. Closing Thoughts

1. An Introduction To Classes In JavaScript

In the previous article in this series we looked at function constructors as one of the recommended ways to set an object's prototype in JavaScript, and we noted that although, there are other ways of doing this, concocting them into one discursive article would be a disincentive to readers.
You can get a refresher from this article here:

In this article we will pick up where we left off by looking at Classes in JavaScript.
This is another recommended method of setting an object's prototype in JavaScript and it makes for a very interesting discourse in this OOP (Object Oriented Programming) in JavaScript series.

Below are some gleanings from a previous post in this series:

The classical inheritance or class bases inheritance involves writing classes; which are like blueprints of objects to be created. Classes can inherit from classes and even create subclasses.

In the prototypal inheritance an object inherits its properties and methods from its prototype object. And sometimes, a search down the prototype chain is necessary for an object to access some properties and methods.

You can get a refresher from this article here:

🎉 So what is interesting is that JavaScript introduced classes in ECMAScript 2015 but is primarily syntactical sugar over JavaScript's existing prototype-based inheritance. The class syntax does not introduce a new object-oriented inheritance model to JavaScript.

Although, one can say in a nutshell; classes in JavaScript are just new syntax for the same old prototypal inhertance. They however, have significant improvements over function constructors and are a very powerful addition to the JavaScript programming language.

Lets take a deeper look at classes in JavaScript in the next section.

2. Classes In Detail

In JavaScript classes are functions. And since all functions are objects in JavaScript, classes are also objects.

Classes are Functions

Kindly run and consider the code below. Be sure to expand the console.log output for a more detailed result

class Person { constructor(name, gender) { this.name = name; this.logo = gender; } } console.log("Person class", Person) console.log("Person prototype", Person.__proto__)

From the results of the code above we can see the follow:

  • The Person class is a function with a name property in this case Person.
  • It has a prototype property which points to the Person {} object. Take note this is not the prototype of the Person class. But this is the prototype of all the instances of the Person class.

💡 The prototype property of a class is not the prototype of the class. It is the prototype of every object created from that class

  • The Person class has a __proto__ property which points to the function prototype. This is the prototype of the Person class.

Lets elaborate on the last two points above with some code samples.
Kindly run and consider the codes below:

class Person { constructor(name, gender) { this.name = name; this.logo = gender; } } console.log("Person class", Person.prototype) console.log("Person prototype", Person.__proto__)

From the results of the code above it should be clear that Person.prototype is different from Person __proto__. The former is the prototype of all instance of the Person class and the latter is the prototype of the Person class itself.

🎉 If this feels a little fast-paced for you, please know that I am building upon what I have already covered in the previous two articles.
However, if you have not been following or for some reason you are finding this section a bit puzzling, kindly refer to my previeous articles in this series. I have already shared their links in section 1.

Like regular functions in JavaScript you can have a class declaration and a class expression.
Kindly examine the code below

// A class declaration
class Person_Dec {
  constructor(name, gender) {
    this.name = name;
    this.logo = gender;
  }
}

// An Unnamed class expression
const Person_Exp = {
  constructor(name, gender) {
    this.name = name;
    this.logo = gender;
  }
}

// Named class expression
const Person_Exp2 = class Person_Exp {
  constructor(name, gender) {
    this.name = name;
    this.logo = gender;
  }
}

All three methods above are valid ways to implement a class in JavaScript.

Classes are Objects

In other programming languages like C#, C++ and Java, that uses classical (class-based) inheritance the class is a template or a blueprint that details the structure of an object. Objects are built from them but they are not objects.

In JavaScript however, the Class is a special function and all functions in JavaScript are objects hence the JavaScript class is an object. JavaScript still uses prototypal inhertance. Classes however, provide a new and improved way to set the objects prototype. We will look at this in a moment.

Kindly consider the results of the code below

class Person { constructor(name, job) { this.name = name; this.job = job; } // Methods getName() { return "Hello My Name is " + this.name ; } //static static getPersonGender () { return "male" } } const developer = new Person("Lawrence Eagles", "developer"); console.log("Developer's Name", developer.getName()); console.log("Developer's Prototype", developer.proto); console.log("Person's gender", Person.getPersonGender()); // console.log("Developer's gender", developer.getPersonGender());

Above is the declaration of a Person Class with a normal method and a
static method.
From the results of running the code, we can see the following.

  • The instance of the Person class (the developer object) inherites the properties and methods of the Person class.

Thus we were able to call the getName method on the developer object.

console.log("Developer's Name", developer.getName());  // calls the getName function inherited from the Person Class
// returns "Lawrence Eagles"
  • Using the static keyword with a method, creates a static method for the class.
//static
  static getPersonGender () {
    return "male"
  }

These methods are not inherited by the instances of a class hence they cannot be called on the instances of a class itself.

You can prove this by uncommenting this line from our code sample:

//console.log("Developer's gender", developer.getPersonGender()); 

Notice however, that we where able to successfully call the getPersonGender method on the Person class itself.

console.log("Person's gender", Person.getPersonGender()); // calls the getPersonGender static method of the Person class 
// This call is successful because the static method is available on the Person class

💡 Static methods aren't called on instances of the class. Instead, they're called on the class itself.
They are often utility functions, such as functions to create or clone objects.

  • The prototype of the instance of the Person class (the developer object) is the Person {} object. We have already noted that the Person object have a prototype property which points to the Person {} object and serves as the prototype of all instances of the Person class. Here we get an elboration on this with codes samples.
console.log("Developer's Prototype", developer.__proto__); // gets the prototype of the developer

3. Subclasses In Detail

Besides creating instance of a class we can also extend a class (create subclasses from an existing class). This is kind of advance and it is a very powerful feature of the JavaScript class.

JavaScript introduced a new keyword for this purpose called extends.

💡 The extends keyword is used in class declarations or class expressions to create a class that is a child of another class.

Kindly run and consider the code below:

class Person { constructor(name) { this.name = name; } sayName() { console.log("My name is " + this.name); } } class Developer extends Person { constructor(name) { super(name); this.name = name; } getBio() { super.sayName(); console.log("I am a developer"); } } let ReactGuy = new Developer("Lawrence Eagles"); ReactGuy.getBio();

The code above shows a Person class and a Developer subclass created from the person class. Below are some comments to elaborate on the code above.

  • In other to create a subclass we extend the parent class using the keyword extends Hence: class Developer extends Person.

  • The super keyword is used to access and call functions on an object's parent. Notice we used super.sayName() in the getBio method of the Developer subclass to call the sayName method of the Person Parent class.

  • Calling super() method inside a contractor would call the constructor of the parent class.
    In our example calling the super method inside the Developer's subclass constructor would call the Person class constructor. Now any arguments passed to the super() would also be passed to the parent class constructor. I will elaborate with a small contrived perspicuous example

Kindly run and consider the code below

class ObjectFactory { constructor(height, width) { this.height = height; this.width = width; } } class Square extends ObjectFactory { constructor(length) { super(length, length); } } const Square1 = new Square() const Square2 = new Square(4) console.log("Square1", Square1) console.log("Square2", Square2)

Above we created a Square subclass from the ObjectFactory class. Notice that the ObjectFactory constructor expects two parameters viz height and width. Values for these are provided when we call super() in the Square subclass.

constructor(length) {
  super(length, length);
}

when the Square1 and Square2 instances where created the argument provided to the length parameter was passed down to super() which calls the parent (ObjectFactory) constructor with these arguments.

const Square1 = new Square()
const Square2 = new Square(4)

The height and width property of the Square1 instance is undefined however, because no argument was provided when it was created by invoking the Square subclass.

const Square1 = new Square() // no argument provided.
  • The super() method must be called in the constructor in a class before the this keyword is made available else we get a reference error. Again I would elaborate with an example.

Note running the code below would throw an error this is delebrate:

class ObjectFactory { constructor(height, width) { this.name = "Object Factory" this.height = height; this.width = width; } } class Square extends ObjectFactory { constructor(length) { this.name = "Square Object" super(length, length); } } const Square1 = new Square()

To get the code to work properly kindly move the super(length, length) above this.name = "Square Object".

  • Unlike instances of a class, static methods can be called on a subclass. Let's elaborate with some code examples.

Kindly run and consider the code below:

class Person { constructor(name) { this.name = name; } static logGreeting() { console.log("Good day " + this.name); } sayName() { console.log("My name is " + this.name); } } class Developer extends Person { constructor(name) { super(name); this.name = name; } getBio() { super.sayName(); console.log("I am a developer"); } } const ReactGuy = new Person("Lawrence Eagles") console.log("Developer Prototype", Object.getPrototypeOf(Developer)) console.log("greeting from developer", Developer.logGreeting()) //console.log("greeting from developer", ReactGuy.logGreeting())

In our code example above we noticed that the
Person Class has a static method viz:

static logGreeting() {
  console.log("Good day " + this.name);
}

This method was successfully called by the developer subclass.

// calls the logGreeting static method of the Person Class in the developer subclass
console.log("greeting from developer", Developer.logGreeting())

Static methods however cannot be called on instances of a class. You can test this by on commenting this line and rerun the code:

//console.log("greeting from developer", ReactGuy.logGreeting())

When a subclass is created from a class, the class becomes its prototype.

We prove this from our code sample above with this line:

console.log("Developer Prototype", Object.getPrototypeOf(Developer))

💡 The Object.getPrototypeOf() method returns the prototype of any object passed to it as an argument.

From the result of our code above we can see that the prototype of the developer subclass is the Person parent class.

Classes In Action

I belive by now we should be pretty comfortable with the JavaScript class.

Classes in JavaScript are more than just a new way to create objects and set their prototype. As we have seen above they come with a number of interesting features.

In addition, they would throw an error when they are called without the
new operator this helps developer to avoid nasty bugs common when using function constructors

Classes power several popular JavaScript frameworks and libraries like React.js and Angular. Their rich feature set paves the way for several advanced design patterns in different JavaScript libraries and frameworks.

Let's see some real life code examples.
Kindly examine the code below

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

Above is the boilerplate code for an ErrorBoundary component in React.
Although, this is not an article on React; I only want us to see classes in action in some of the most advanced JavaScript library.

💡 React 16 introduces a new concept of an “error boundary”, in other to catch error anywhere in your application and render a fallback UI instead of allowing the whole applicaiton to crash because of a JavaScript error.
It is an advance design pattern in React.js

  • Notice the ErrorBoundary component is implemented with a JavaScript class by extending the React Component class.
class ErrorBoundary extends React.Component
  • Notice how super(props) was called before this keyword was used in the constructor.
constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  • Notice the static method getDerivedStateFromError of the ErrorBoundary subclass. It is used to render a fallback UI after an error has been thrown
static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

If you are not a React.js Developer you may not fully comprehend what is going on here, but that is understandable. My aim here is to show you some of the real world implementation of classes in regards to our discourse.

5. Closing Toughts:

It has really been a long article and if you got here you are really appreciated and I am more than elated.

I really hope that at this point, you can see the benefits of our long discussion and at least got a thing or two from this article. If so, I would be honoured if you would share it with you friends and colleagues, while also looking forward to hearing your opinions, comments, questions, or requests (in case anything is not clear) at the comments section below.

Discussion

pic
Editor guide