Arrays without prototypes: impossible to construct performantly?

I'm working on deep-frozen data structures made up of objects and arrays, and it would be ideal in my mind for a deep-frozen structure not to have the mutable Object and Array prototypes hanging off of each otherwise-immutable node.

In practice though while I can easily construct an Object with no prototype, and I can construct an array with no prototype, I cannot construct the array with anything close to reasonable performance because the only way I know of to do it is Object.setPrototypeOf([], null).

Once you do this though the good news is that things work correctly, e.g:

JSON.stringify(Object.setPrototypeOf([1, 2, 3], null)) === '[1,2,3]';

Was there ever a consideration of supporting something like Array.create(null)? Is there a better way to do this that I just don't know about?

From my dirt-simple benchmarks, creating the no-proto array using setPrototypeOf is a 60x slowdown over creating a normal array, but monomorphic access to the array once created is normal speed. So as far as I can tell the only barrier to being able to use no-proto structures more widely is that we have no good way to make arrays.

If I could just make one no-proto array and then get copies of it! But so far all the API's I've tried like slice and structuredClone return a copy that has the regular Array prototype instead of the null prototype with the Array brand like I have at the start and am hoping to get at the end.

AFAIK the only reasonably-fast way to make an array with a non-native prototype is subclassing, but it still doesn't create null-prototype arrays, and having a frozen object as prototype unfortunately makes accesses slower.

class SafeArray extends Array {
  static {
    delete this.prototype.constructor;
    Object.freeze(Object.setPrototypeOf(this.prototype, null));
  }
}

new SafeArray();
4 Likes

Oh this is interesting! I hadn't thought of this, but I do think it solves my problem.

Most importantly it allows me not to include all the normal array prototype methods as part of my contract for data structures.

I don't think having a frozen object as a prototype will make accesses any slower unless it creates runtime polymorphism, and for me this wouldn't create any runtime polymorphism.