JS equivalent to Java's Collections.unmodifiableCollection

133 views Asked by At

I often use this strategy to my java code in order to make a Collection read only for the outside world, but avoid big/often clonings:

public abstract class MyClass {
    List<Long> myIds;

    public Collection<Long> getIds() {
        return Collections.unmodifiableCollection(this.myIds);
    }
}

I would like to follow the same pattern in my JS classes. This would make my business logic code much safer and cleaner, since I would be able to controll every change to my lists (push/splice etc) within the class that owns the fields.

At the moment use "private" fields for the lists and get-set functions for accessing them from outside. The only missing link is some equivalent to java's Collections.unmodifiableCollection. Something that would not copy the whole list (such as slice()), and that would not affect the original field (such as Object.freeze()).

Is there such a feature in JS? If not, how could someone achieve a similar effect (custom iterables?)

2

There are 2 answers

0
CertainPerformance On BEST ANSWER

If you don't want to copy the object, and you want to make sure the original object can't be mutated externally, one option is to return a Proxy which throws when anything tries to mutate:

const handler = {
  get(obj, prop) {
    return obj[prop];
  },
  set() {
    throw new Error('Setting not permitted');
  }
}
class MyClass {
  _myIds = ['foo', 'bar']
  getIds() {
    return new Proxy(this._myIds, handler);
  }
}

const instance = new MyClass();
const ids = instance.getIds();
console.log(ids);
// Error:
// ids[2] = 'baz';
// Error:
// ids.push('baz');

Of course, instance._myIds is still technically visible - if you want to prevent that, you can use something like a WeakMap to ensure that the private array is truly only visible from inside the class.

But Proxies are a bit slow. You might consider something like Typescript's ReadonlyArray instead - that way, you ensure that the code doesn't contain anything that mutates the array after it's returned, while keeping the actual code fast at runtime:

private myIds = ['foo', 'bar']
public getIds() {
  return this.myIds as ReadonlyArray<string>;
}
4
Dan On

If you are not limited to use only JS solutions, then you can try immutable.js (https://github.com/immutable-js/immutable-js).

From the docs:

Immutable.js provides many Persistent Immutable data structures including: List, Stack, Map, OrderedMap, Set, OrderedSet and Record

Here is simple example with Immutable.List:

const list1 = Immutable.List([ 1, 2 ]);
const list2 = list1.push(3, 4, 5);
const list3 = list2.unshift(0);
const list4 = list1.concat(list2, list3);

console.log('list1:', list1); // [1,2]
console.log('list2:', list2); // [1,2,3,4,5]
console.log('list3:', list3); // [0,1,2,3,4,5]
console.log('list4:', list4); // [1,2,1,2,3,4,5,0,1,2,3,4,5]
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.2/immutable.js"></script>

Updated at Mon, 27 May 2019 09:55:43 GMT