2

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?)

Alkis Mavridis
  • 1,090
  • 12
  • 28

2 Answers2

3

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

Dan
  • 291
  • 1
  • 9
  • I would be grateful if you could share your thoughts and tell me what you think is wrong in my answer. Perhaps I misread the question? Don't just vote down like that, please. – Dan May 27 '19 at 09:08
  • I am not the downvoter, but the question askes for a feature in JS, so a pure JS solution is preferred. Also, you might want to provide some short code to demonstrate how the library solves the specific case asked in the question. – blaz May 27 '19 at 09:12
  • 1
    @blaz Thanks for the explain! I did not notice that pure JS solution is needed. I will update my answer. – Dan May 27 '19 at 09:43
  • @PaulStenne thanks! I appreciate constructive criticism. – Dan Jun 01 '19 at 11:25
3

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>;
}
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320