How to protect a JS array against truncation
Andrey Smolko
Posted on August 29, 2022
Disclaimer: There are several options how to truncate an array in JS but here I would like to talk only about one of them.
If you want to truncate an array in JS there is a trick with the length property:
const numbers = [1, 2, 3, 4, 5];
if (numbers.length > 3) {
numbers.length = 3;
}
console.log(numbers); // [1, 2, 3]
console.log(numbers.length); // 3
Sometimes I like to set a small theoretical JS problems for myself to keep fit.
So let's do a small exercise together and try to find a solution how to protect an array and disable its mutation by length property manipulation.
1st solution:
A length property of an array by default has following attributes:
enumerable === false
configurable === false
writable === true
When a property attribute configurable is false just a few changes to a property is allowed. One of them is to change attribute writable to false.
A combination of attributes configurable===false and writable === false makes impossible to delete or modify a property by any possible ways.
const numbers = [1, 2, 3, 4, 5];
Object.defineProperty(numbers,'length',{writable:false})
if (numbers.length > 3) {
numbers.length = 3;
}
console.log(numbers); // [1, 2, 3, 4, 5]
console.log(numbers.length); // 5
Object.defineProperty(numbers,'length',{value:3})
// Uncaught TypeError: Cannot redefine property: length
The goal is achieved! An array mutation by property length is disabled.
2st solution (kind of exotic):
Let's check what ES specification says about array length manipulation :
For each own property key P of A that is an array index, whose numeric value is greater than or equal to newLen, in descending numeric index order, do:
To put it simply, a truncation of an array starts from a tail of an array (in descending numeric index order). In our first code snippet element 5 will be deleted first and then element 4.
Now it is time to remember that in JS an array is an object (exotic one but still an object) where an index is a key of a property and an element is a value of a property.
So when an array is truncated it means that array's properties whose keys greater or equal than a new length are deleted.
Let deleteSucceeded be ! A.[[Delete]](P).
Here A is an array, [[Delete]] is an abstract method to delete property from an object, P is a property key.
So let's combine together.
In our example with numbers array a property with key === 4 would de deleted first. But there is a way to prevent deletion of a property from an object - set attribute configurable to false!
Experiment:
const numbers = [1, 2, 3, 4, 5];
Object.defineProperty(numbers,'4',{configurable:false})
if (numbers.length > 3) {
numbers.length = 3;
}
console.log(numbers); // [1, 2, 3, 4, 5]
console.log(numbers.length); // 5
The array is not mutated!
According the specification (see step 17) an array truncation due to length property reduction stops when a first unsuccessful property deletion is met.
So if we protect from deleting just last array element then we achieve our goal - prevent array mutation.
P.S. [[Delete]] abstract method which is used to defined array truncation is also used to define logic of delete operator. So when you reduce length of an array you literally delete properties from it. As a side effect, pop() method also will be disabled for such array.
P.S.S. I have never used that in my work and hope never will=) But I enjoy feeling that I understand a little bit how internal gears of JS are designed.
Posted on August 29, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.