I have the following as a part of a module (names simplified for purpose of question):
In "module.js":
var _arr;
_arr = [];
function ClassName () {
var props = {};
// ... other properties ...
props.arr = {
enumerable: true,
get: function () {
return _arr;
}
};
Object.defineProperties(this, props);
Object.seal(this);
};
ClassName.prototype.addArrValue = function addArrValue(value) {
// ... some code here to validate `value` ...
_arr.push(value);
}
In "otherfile.js":
var x = new ClassName();
With the implementation above, and the sample code below, adding values to arr
can be achieved in two ways.
// No thank you.
x.arr.push("newValue"); // x.arr = ["newValue"];
// Yes please!
x.addArrValue("newValue"); // Only this route is desired.
Does anyone know how to achieve a readonly array property?
Note: writeable
is false by default and no difference is observed if I explicitly set it.
Object.freeze() will do what you're asking (on browsers properly implementing the spec). Attempts to modify the array will either fail silently or throw TypeError
when in strict mode.
The easiest solution is to return a new frozen copy (freeze is destructive):
return Object.freeze(_arr.slice());
However, if more reading than writing is expected, lazy-cache the most recently accessed frozen copy and purge upon write (since addArrValue
controls writes)
Lazy caching read-only copy using modified original example:
"use strict";
const mutable = [];
let cache;
function ClassName () {
const props = {};
// ... other properties ...
props.arr = {
enumerable: true,
get: function () {
return cache || (cache = Object.freeze(mutable.slice());
}
};
Object.defineProperties(this, props);
Object.seal(this);
};
ClassName.prototype.addArrValue = function addArrValue(value) {
// ... some code here to validate `value` ...
mutable.push(value);
cache = undefined;
}
Lazy caching read-only copy using ES2015 classes:
class ClassName {
constructor() {
this.mutable = [];
this.cache = undefined;
Object.seal(this);
}
get arr() {
return this.cache || (this.cache = Object.freeze(this.mutable.slice());
}
function addArrValue(value) {
this.mutable.push(value);
this.cache = undefined;
}
}
A "transparent" re-usable class hack (rarely required):
class ReadOnlyArray extends Array {
constructor(mutable) {
// `this` is now a frozen mutable.slice() and NOT a ReadOnlyArray
return Object.freeze(mutable.slice());
}
}
const array1 = ['a', 'b', 'c'];
const array2 = new ReadOnlyArray(array1);
console.log(array1); // Array ["a", "b", "c"]
console.log(array2); // Array ["a", "b", "c"]
array1.push("d");
console.log(array1); // Array ["a", "b", "c", "d"]
console.log(array2); // Array ["a", "b", "c"]
//array2.push("e"); // throws
console.log(array2.constructor.name); // "Array"
console.log(Array.isArray(array2)); // true
console.log(array2 instanceof Array); // true
console.log(array2 instanceof ReadOnlyArray); // false
A proper re-usable class:
class ReadOnlyArray extends Array {
constructor(mutable) {
super(0);
this.push(...mutable);
Object.freeze(this);
}
static get [Symbol.species]() { return Array; }
}
const array1 = ['a', 'b', 'c'];
const array2 = new ReadOnlyArray(array1);
console.log(array1); // Array ["a", "b", "c"]
console.log(array2); // Array ["a", "b", "c"]
array1.push("d");
console.log(array1); // Array ["a", "b", "c", "d"]
console.log(array2); // Array ["a", "b", "c"]
//array2.push("e"); // throws
console.log(array2.constructor.name); // "ReadOnlyArray"
console.log(Array.isArray(array2)); // true
console.log(array2 instanceof Array); // true
console.log(array2 instanceof ReadOnlyArray); // true