DEV Community

Andi Rosca
Andi Rosca

Posted on • Originally published at godoffrontend.com

Organize your JavaScript OOP code better with this functional programming trick

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;
}
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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".

Top comments (2)

Collapse
 
bias profile image
Tobias Nickel • Edited

What do you think of Object.freeze to ensure props don't change over time? I think that is awesome, but never saw it anywhere,... and people rather use libs like immutable.

Collapse
 
andi23rosca profile image
Andi Rosca

I was originally going to talk about freezing but then I decided to scope the article down to only the bare essentials.

I know if you do a freeze on the whole class instance it has weird effects on subclasses and inheritance in general. But it is a pretty good idea to "ensure" that nobody can mutate stuff.

I do think that in some cases you might want to keep your classes mutable though. Just depends on the use case and what you're using them for.