Faking private class constructors in JavaScript
Tags: javascript
Published: 2023-03-03 05:21:27

---
title: Faking private class constructors in JavaScript
published: true
description: You can simulate a private constructor in JS by requiring the constructor to accept something that is unavailable to the rest of the code. Here's how.
tags: javascript
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-03-03 02:53 +0000
---

Private class constructors can be helpful, but unfortunately JavaScript doesn't support them. The Mozilla Developer Network site suggests [simulating private constructors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields#simulating_private_constructors) by mutating static properties on the base class, but the implementation feels hacky.

## Faking private constructors

You can simulate a private class constructor in JavaScript by requiring the constructor to accept something that is unavailable to the rest of the code. We can use an immediately invoked function expression to do just that.

```js
const PrivateConstructor = (function () {
  class PrivateParams {
    constructor(param1, param2) {
      this.param1 = param1;
      this.param2 = param2;
    }
  }

  return class PrivateConstructor {
    constructor(privateParams) {
      if (!(privateParams instanceof PrivateParams)) {
        throw new TypeError(
          "PrivateConstructor is not constructable. " +
          "Use PrivateConstructor.make()."
        );
      }

      this.param1 = privateParams.param1;
      this.param2 = privateParams.param2;
    }

    static make(param1, param2) {
      return new PrivateConstructor(
       new PrivateParams(param1, param2)
      );
    }
  };
})();

// Example usage:
PrivateConstructor.make(1, 2);

// Throws error:
new PrivateConstructor(1, 2);
```

## How it works

The code above executes an immediately invoked function expression, and it returns the `PrivateConstructor` class that is defined within its scope. The `constructor` method requires a `PrivateParams` instance, and if it receives anything else it will throw an error. The `PrivateParams` class is only available within the immediately invoked function expression: nothing outside of its scope can instantiate the class. 

This leaves only one way to instantiate the class: the static `.make()` method, which passes in a `PrivateParams` instance, satisfying the constructor and successfully returning an instantiated instance of the `PrivateConstructor` class.

## Other approaches

Another option to restrict a class's instantiation could be to use a symbol that is only available within the scope of the class as an argument to the constructor. Symbols are guaranteed to be unique by the language, so it couldn't be forged.

```js
const PrivateConstructor = (function () {
  const _instantiationToken = Symbol();

  return class PrivateConstructor {
    constructor(param1, param2, instantiationToken) {
      if (instantiationToken !== _instantiationToken) {
        throw new TypeError(
          "PrivateConstructor is not constructable. " +
          "Use PrivateConstructor.make()."
        );
      }

      this.param1 = param1;
      this.param2 = param2;
    }

    static make(param1, param2) {
      return new PrivateConstructor(
        param1,
        param2,
        _instantiationToken
      );
    }
  };
})();

// Example usage:
PrivateConstructor.make(1, 2);

// Throws error:
new PrivateConstructor(1, 2);
```

While less code than the first example, having a separate parameter for an access token feels hacky, and somehow feels like an even leakier abstraction that we're trying to shore up shortcomings in JS itself.

## Should you do this?

Probably not. Certainly don't do this in application code if you can avoid it. Both of these examples are clever code, and committing clever code to a project is one of the leading causes of coworkers hating you.

Clever code like this may be appropriate in a library or framework, but be careful before you implement it in your own project.

## Honorable mention: TypeScript

I should mention that unlike JavaScript, TypeScript _does_ have this capability. Here's what that looks like:

```ts
class PrivateConstructor {
  param1: number;
  param2: number;

  private constructor(param1: number, param2: number) {
    this.param1 = param1;
    this.param2 = param2;
  }

  static make(param1: number, param2: number) {
    return new PrivateConstructor(param1, param2,);
  }
};
  
  
// Example usage:
PrivateConstructor.make(1, 2);

// Throws compiler error:
new PrivateConstructor(1, 2);
```

This is pretty nice: it avoids the clever code problem and limits external access to the constructor. Unfortunately your project might now be in TypeScript. You can't win them all.