Categories
JavaScript Best Practices

JavaScript Clean Code — Objects and Classes

Spread the love

When writing clean code we have to be careful about the objects and classes or constructors that we define. We don’t want to expose data that we don’t want the outside world to view, and we want our data structures to be defined in a way that’s easy to reuse.

In this article, we’ll look at how to organize our data in a way that’s easy for us to use.

Use Classes Over Constructor Functions

We should use classes instead of constructor functions. It’s much clearer and inheritance can be done much more easily.

They’re the same underneath. Class syntax is syntactic sugar for the constructor function syntax.

For example, we can define a Person class as follows:

class Person{
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

We can see that our constructor takes in name and age as parameters and set it as values of instance variables.

Where it shines is when we need to extend the class. We can create an Employee class that has the instance variables of the Person class and extends it as follows:

class Employee extends Person {
  constructor(name, age, employeeCode) {
    super(name, age);
    this.employeeCode = employeeCode;
  }
}

All we have to do is to call the superclass’ constructor with the super call, and then we can set the instance variables of the Employee class in the constructor.

Using Getters and Setters

Getters and setters are good for hiding the actual data that’s being accessed. We can have computed properties, which means that we can abstract way the implementation of those computed properties so that we don’t have to worry about changing the code that uses them when we change the implementation.

Also, setters let us validate before setting the data rather than setting them directly.

Likewise, error handling and logging can also be added easily into them.

We can also lazy load object properties with getters.

For example, we can use getters as follows:

class Rectangle {
  constructor(length, width) {
    this._length = length;
    this._width = width;
  }

  get area() {
    return this._length * this._width;
  }
}

As we can see, we can use it to create a computed property to get the area of a rectangle.

Then we can use it as follows:

let rectangle = new Rectangle(1, 2);
console.log(rectangle.area);

We can add validation into setters as follows:

class Rectangle {
  constructor(length, width) {
    this._length = length;
    this._width = width;
  }

  get area() {
    return this._length * this._width;
  }

  get length() {
    return this._length;
  }

  set length(length) {
    if (length <= 0) {
      throw new Error('Length must be bigger than 0');
    }
    this._length = length;
  }

  get width() {
    return this._width;
  }

  set width(width) {
    if (width <= 0) {
      throw new Error('Width must be bigger than 0');
    }
    this._width = width;
  }
}

Note that it’s also a good idea to define getters so that we can access the properties’ values.

Keeping Members Private

There’re no private variables in JavaScript classes, so we should define private variables in block scope so that they won’t be exposed to the public with let and const .

Chaining Methods

Chaining methods make calling a series of functions less verbose. We can define a function that can be chained by returning this .

For example, we can write the following:

class Person {
  setName(name) {
    this.name = name;
    return this;
  }

  setAge(age) {
    this.age = age;
    return this;
  }
}

Then we can use it as follows:

const person = new Person().setName('Joe').setAge(10);

Then when we log person , we get:

{name: "Joe", age: 10}

This is a common pattern that’s used in many libraries like jQuery and Lodash.

Preferring Composition Over Inheritance

We should leave a class inheritance for ‘is-a’ relationships only. An is-a relationship is a subset of some bigger entity. For example, an employee is a person.

Otherwise, we should use composition instead. For example, if we want to keep the address for a Person, we can create a class called Address and instantiate it in the methods of the Person class and use it there instead.

A Person has a ‘has-a’ relationship with Address , so we shouldn’t use inheritance in this case.

For example, we can write something like the following code:

class Address {
  constructor(streetName) {
    this.streetName = streetName;
  }
}

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  setAddress() {
    const address = new Address('123 A St.');
  }
}

Conclusion

The class syntax is much easier to understand than the constructor function syntax, especially when we need to do inheritance. Therefore, it’s time to move away from the constructor function syntax as fast as we can.

We should only use class inheritance in ‘is-a’ relationships. Otherwise, we should use composition instead.

Also, we can use getters and setters to keep the implementation of the members underneath private. We can use getters for computed properties and setters for running code before setting values to a member. For example, we can run some validation code before setting the value with a setter.

Chaining methods is also a commonly accepted way to clean up code by making calls less verbose.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *