Make readonly TypedArray

> Object.freeze(Uint8Array.of(1, 2, 3, 4))
Uncaught TypeError: Cannot freeze array buffer views with elements
    at Function.freeze (<anonymous>)

Current, Object.freeze not allow freeze TypedArray.

Expose TypedArray, hope it can't be modified externally

  1. Extend Object.freeze supports TypedArray
  2. Add a method to TypedArray (%TypedArray%.prototype.readonly())
  3. Nope (Suggest, Copy object?, e.g: return %TypedArray%.from(%TypedArray%))
1 Like

There're two things:

Readonly-view to a mutable ArrayBuffer

Prevent changes from the view, thus it can be passed around safely

Froze ArrayBuffer itself

Makes all views read-only.

Does anyone interested?
With a frozen ArrayBuffer, the engine can structured-clone them to another realm by sharing the memory directly instead of full copy.

1 Like

We can already call Object.freeze on an ArrayBuffer right? It's just that it doesn't have any properties to freeze in the first place.

I'd like to see the second option. It's important to have really immutable structures for cryptography to store private and public keys, curves params, initialization values, etc.

1 Like

Both of them have valid use cases. Let me try to make a proposal for it.

I think it should be two separate proposals with links to each other. Due to existing ability to implement read-only views with Proxy object, what makes one part of proposal some kind of controversial. Also it might take more time to implement and develop two proposals which have in common just a concept of read-only behavior and too much in differ.

Can you give me an example? I don't think it is possible

Sure. Here is is how you can create read-only Uint8Array-alike object:

let array = new Uint8Array(1);
let proxy = new Proxy(array, {
  get(self, prop) {
    return self[prop]
  set(self, key, value) {
    // Do nothing

array[0] = 1;

console.log(proxy[0]); // 1

proxy[0] = 2;

console.log(proxy[0]); // 1

Proxy could be modified to throw on set attempt.

Yes your example prevents the modification but it makes the object no longer a Uint8Array. Thus it will not pass the brand check, e.g. if some Web API needs a real Uint8Array.

Yeah, you're right about WebAPI. It seems like there would be dozens of issues with this.

I think there should be some more general solution like Object.freezeIndices, or an option to Object.freeze method to freeze only indices. Actually this is what strings do now, they can receive and change own properties but not indices. It would solve the problem in pretty straightforward way:

const buffer = new ArrayBuffer(8);
const view = new Uint8Array(buffer);

Object.freezeIndices(view); // Make Uint8Array instance read only.
Object.freezeIndices(buffer); // Make ArrayBuffer instance read only.
// or
Object.freeze(view, {indices: true}); // Make Uint8Array instance read only.
Object.freeze(buffer, {indices: true}); // Make ArrayBuffer instance read only.

As a side effect it should work for regular arrays and any other array-like objects too.

Actually freezing indices does not work:

new Uint8Array(view.buffer) // oops, a readwrite view again

Now I got what you want. I think it might be achievable with some wrapper over ArrayBuffer to make it interoperable and do what you expect:

let buffer = new ArrayBuffer(1)
let view = new Uint8Array(new ArrayBufferWrapper(buffer))

new Uint8Array(view.buffer) // 👍 Now it's fine

* ArrayBufferWrapper – is example name and should be changed to something more suitable.

Actually you already use such wrapper but under the hood and without clear design of how it should be implemented. In the case of wrapper it's easier to use it without some magic, also it could be brand-checked in JavaScript and type-checked in TypeScript, if API requires such buffer to be identified as writable or readonly. And it looks more straightforward to JS design itself. Also It seems like it solves both of raised problems: it allows construct read-only views and implement immutable buffers behavior, like this:

function makeImmutableBuffer(source) { ​
 ​return new ArrayBufferWrapper(Uint8Array.from(source).buffer); // Now there is no access to mutable buffer

IMO option {readonly: true} makes typed arrays interface inconsistent and complex. This option would be redundant in case of number as the first argument and it's not clear what to do in the case when it presented: ignore it or throw an error.

Object.freeze cannot add new parameter

This a break change: e.g: [...].map(Object.freeze)


i want's %TypedArray%.prototype.freeze() returns a copy.
but not freeze the origin object.

like Collections (Java Platform SE 7 )

So I'm assuming the usage of TypedArrays needs high performance. Implicitly copy is not so good

Not an opinion, just food for thought: does this start a pattern of adding a .freeze() to things like Map and Set ?

Suggest open a new thread, discuss this problem. (Promote to Map and Set)


But it means any function in JS couldn't increase its arity and almost any enhancement is breaking. And I couldn't agree on that. No one should pass wrong number of arguments into a function and expect it to work properly.

This is what's written in the spec about callback function (ECMAScript® 2022 Language Specification):

callbackfn should be a function that accepts three arguments...

To use functions with different arities developers should use a wrapper:

[].map(v => Object.freeze(v))

So example you just provided shouldn't be decided as valid code. Maybe you have better example?

unfortunately, this is indeed break changes.