2

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?

Nika Kurashvili
  • 6,006
  • 8
  • 57
  • 123
  • 3
    This is not Singleton pattern. Objective of `Object.freeze` is to make an object immutable. Hence the error – Rajesh Apr 08 '20 at 09:26
  • I would personally do `import test from './test'` , just to be consistent and save any confusion later.. – Keith Apr 08 '20 at 09:33

2 Answers2

3

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.
Community
  • 1
  • 1
etarhan
  • 4,138
  • 2
  • 15
  • 29
  • You could do `setData(arr) { this.data.push( ...arr ) }` as well – Rajesh Apr 08 '20 at 09:38
  • So, object.freeze is not about singleton at all. and there's no way not to be able to change value outside a class, but inside a class in javascript right? – Nika Kurashvili Apr 08 '20 at 09:38
  • @Rajesh great suggestion, I've updated my answer with your example. – etarhan Apr 08 '20 at 09:40
  • @NikaKurashvili Object.freeze will not allow you to remove, add or reassign any property of a particular object period once you freeze it. Since it is shallow though, you can still update the underlying array the way you please. It does not necessarily have to do with the Singleton pattern – etarhan Apr 08 '20 at 09:42
  • Looks like there's no way of what I asked. – Nika Kurashvili Apr 08 '20 at 09:42
  • @NikaKurashvili You could use the technique described here to achieve what you want: https://stackoverflow.com/a/28165599/10353987 – etarhan Apr 08 '20 at 09:47
3

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)
Community
  • 1
  • 1
Rajesh
  • 24,354
  • 5
  • 48
  • 79