javascript singleton how to change properties inside a class

Viewed 881

I have seen that people use Object.freeze alongside some other techniques to make a class Singleton.

Let's say I have this class.

class Test {
    constructor(){
        this.data = [];
    }

    setData(){
        this.data = [1,2,3];
    }

}

const test = new Test();
Object.freeze(test);
export default test;

Now, I import this into another file.

import Test from './test'
Test.setData();

This now causes error that Cannot assign to read only property to data which means that I can't even use setters/getters for my properties.

Question 1) why do we use Object.freeze if we can't even change properties inside a class?

Question 2) what would i do to be able not to change properties outside a class, but inside a class?

2 Answers

Using Object.freeze

In case you were trying to implement something as described here. The difference between that approach and what you are doing here is that you should be updating the data array instead of reassigning it completely. Since Object.freeze we essentially perform a shallow freeze, you are allowed to push and pop on the data array, but not reassign it (which is what you are doing in your example). So the following would work:

class Test {
    constructor(){
        this.data = [];
    }

    setData(data) {
        this.data.push(...data);
    }

}

const test = new Test();
Object.freeze(test);
export default test;

And then you could add your data like so:

import Test from './test'
Test.setData(1,2,3);

Private properties

Since I understand that your aim is to actually make private properties that are unable to be updated outside of your singleton, you could also follow the approach outlined in this answer SO Answer. This would look like the following in your case:

class Test {
    constructor(){
        let data = [];
        this.setData = (newData) => data = newData;
        this.getData = () => data;
    }
}

const test = new Test();
test.setData([1,2,3]);

console.log(test.getData());
Since the data field is not exposed directly but instead only in your constructor scope, the only way to update or retrieve it is using setData and getData, which is what you were aiming to do.

As commented:

This is not Singleton pattern. Objective of Object.freeze is to make an object immutable. Hence the error.

Singleton:

It means a class can have only one instance which is shared across application. Usually, it means the constructor is private and doing new will throw error.

However, in JS as of now, you cannot have private constructor. So you can try this approach:

Idea:

  • During compile-time, you will create an object and store its reference.
  • Whenever you try to create its instance, you will return the originally created object's reference. This way, all attempts will point to same object, aka singleton.

You do not need Object.freeze, unless you wish to make object immutable.

class Test {
  static _instance = null;
  static getInstance() {
    return Test._instance || new Test();  
  }

  constructor() {
    this.data = [];
    if (Test._instance === null) {
      Test._instance = this;
      return this;
    }
    else return Test._instance;
  }
  
  setData(data) {
    this.data = data;
  }
}

const t1 = new Test();
const t2 = new Test();

t2.setData([1,2,3]);

console.log(t1.data, t2.data)

t1.setData('This is a test')

console.log(t1.data, t2.data)

Related