DEV Community

John Au-Yeung
John Au-Yeung

Posted on • Originally published at thewebdev.info

JavaScript Best Practices — Improving Classes

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

Subscribe to my email list now at http://jauyeung.net/subscribe/

Cleaning up our JavaScript code is easy with default parameters and property shorthands.

In this article, we’ll look at the best practices for creating classes and when we should create them.

Constructors

There are a few things that we should do to make our constructors better. They are the following.

Initialize All Member Data in All Constructors If Possible

We should put them all in the constructor so that they’re all initialized when we instantiate the object.

So we can write:

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}
Enter fullscreen mode Exit fullscreen mode

Now we make sure that everything’s initialized with a value.

Create a Singleton In the Constructor

If we need only one instance of a constructor, then we can create one instance of it.

For instance, we can write the following:

class Person {
  constructor(name) {
    if (this.instance) {
      this.instance = {
        name
      }
    }
    return this.instance;
  }
}
Enter fullscreen mode Exit fullscreen mode

In the code above, we return the object that we created if this.instance isn’t defined yet.

Otherwise, we return whatever it’s set to this.instance .

Prefer Deep Copies to Shallow Copies Until Proven Otherwise

Deep copies copy everything, so that’s a lot better than doing a shallow copy. Shallow copies leave some things referencing the original object.

That’s not good if we want a true copy.

Therefore, we’ve to make our code to make deep copies as follows:

const copy = obj => {
  const copied = {
    ...obj
  };
  for (const k of Object.keys(obj)) {
    if (typeof obj[k] === 'object') {
      copied[k] = {
        ...copied[k]
      };
      copy(copied[k]);
    }
  }
  return copied;
}
Enter fullscreen mode Exit fullscreen mode

We just use the spread operator to copy nested objects if one is found. And do the same thing recursively.

Then we return the object that we copied.

When Should We Create a Class?

We shouldn’t always create classes. There a few scenarios where it makes sense to create a class.

Model Real-World Objects

Classes are great for modeling real-world objects since they model the behavior of objects

They let us encapsulate instance variables and methods into one package to store state and do actions on objects respectively.

Model Abstract Objects

Likewise, we can use classes to model abstract objects.

They can be used to make abstractions, which are generalizations of different kinds of objects.

Classes are great for holding shared members of subclasses. And subclasses can inherit from them.

However, we should keep the inheritance tree simple so that people won’t be confused with the code.

Reduce Complexity

We can use classes to reduce the complexity of a program.

Classes are great for hiding information. In JavaScript, there’re no private variables in classes yet, so we’ve to hide data in methods instead.

We can then minimize coupling between different parts of our program with that.

Hide Implementation Details

Methods are also good for hiding implementation details.

We can hide the details within methods and only run things that are needed.

To do that, we can nest functions and variables inside methods.

Limit Effects of Changes

The effects of changes can be reduced since we can hide things.

As with hiding implementation, the effects of changes can be isolated by limiting the effects of changes within methods.

Hide Global Data

Global data can become private data by putting them inside the methods of a class.

Then they don’t have to be exposed to the public. All we have to do is to use let and const to declare them within methods.

Streamline Parameter Passing

If we have the same parameters passed into different functions, then we can change the parameters to instance variables and the functions to methods.

For instance, if we have:

const speak = (name) => `${name} spoke`;
const greet = (name) => `hi, ${name}`;
Enter fullscreen mode Exit fullscreen mode

Then we can put the methods into their own class as follows:

class Person {
  constructor(name) {
    this.name = name;
  }
  speak() {
    return `${this.name} spoke`;
  }
  greet() {
    return `hi, ${this.name}`;
  }
}
Enter fullscreen mode Exit fullscreen mode

Now we don’t have to pass in name everwhere.

We just make an instance of Person and call those methods without passing in any arguments.

Conclusion

We can create classes to encapsulate data and package things together. However, we shouldn’t create classes for everything.

Also, we should make deep copies rather than shallow copies whenever possible.

Top comments (0)