Organize your JavaScript OOP code better with this functional programming trick - QuickTips

Pure functions

One of the main characteristics of functional programming is the concept of pure functions.

Pure functions:

  • always return the same value when called with the same arguments
  • never change variables that are outside of their scope

This is what a pure function looks like:

function add(a, b) {
  return a + b;
}

Class methods

A class method is generally the opposite of a pure function.

The purpose of class methods is usually to operate in some way on a class instance (they can do other things of course).

class Point {
  add(point) {
    this.x += point.x;
    this.y += point.y;
    return this;
  }
}
const point = new Point(2, 3);
const point2 = point.add(new Point(3, 4));

How things can get messy

An often encountered problem when using methods in this way is that you are always working on the same class instance. This is fine when you only have one or two objects you're using.

But the more operations and objects your code needs to use, the more you need to keep track of references to instances and making sure you're not mutating objects that are also referenced by other parts of the code accidentally.

Look at this moment.js code for example:

const startedAt = moment();
const endedAt = startedAt.add(1, "year");

console.log(startedAt); // > 2020-02-09T13:39:07+01:00
console.log(endedAt); // > 2020-02-09T13:39:07+01:00

They both log the same time because you unwittingly mutate the startedAt variable as well, since the add method behaves much like the add method I defined on the above Point class.

Seems like an easy problem to spot, but imagine if you have around 15 lines of operations involving multiple objects. Time to whip out the console.logs on every other line.

A better way

With these simple examples, the answer should be clear: You should make your methods "pure".

I say "pure" because the characteristic we care most about is that they should not mutate any values.

Let's rewrite the Point's class add method:

class Point {
  add(point) {
    return new Point(this.x + point.x, this.y + point.y);
  }
}

Now instead of returning a reference to the same instance, you create a whole new instance.

You can chain methods all you want now since you don't need to worry about mutating existing variables.

const point1 = new Point(2, 3);
const point2 = new Point(3, 4);
const point3 = point1
  .add(point2)
  .add(point1)
  .add(point2);

Structuring your classes this way will also make debugging and unit testing way easier, since now you can test the output directly, instead of checking for variables getting mutated properly.

Another nice benefit is that it works nicely with the reactivity systems of libraries like Vue and React. Since creating new instances will be sure to trigger their "reactiveness".

Liked the tip? Consider subscribing to get the latest updates.