Trouble with JavaScript array

JavaScript arrays are “ancient” objects. They were one of the first data types added to the language, unlike Map, Proxy, WeakMap etc.

An object has two types of properties – public slots and internal slots. E.g. Map() has internal property called [[MapData]] where it keeps all it’s data. Array has no internal slots.

However, array does “process” some properties… like index. If the index is >=0, it considers them to be part of the “array”. However, if we use non positive integer values, array adds the property but does not consider them in certain scenarios, e.g. ‘array.length’ or ‘for of’.

Look at some array quirks

// Normal usage
console.log("const normalArray = [11, 23, 35];");
const normalArray = [11, 23, 35];
console.log(normalArray);
/* [ 11, 23, 35 ] */
// Adding by index
console.log("normalArray[3] = 48;");
normalArray[3] = 48;
console.log(normalArray);
/* [ 11, 23, 35, 48 ] */
// Non positive index get added as keys
console.log("normalArray[-1]=5");
normalArray[1] = 5
console.log(normalArray);
/* [ 11, 23, 35, 48, '-1': 5 ] */
// Even string
normalArray['foo'] = 'bar';
console.log("normalArray['foo'] = 'bar';")
console.log(normalArray);
/* [ 11, 23, 35, 48, '-1': 5, foo: 'bar' ] */
// for of gives only positive index
console.log("for (let index of normalArray)");
for (let element of normalArray)
console.log(element);
/*
11
23
35
48
*/
// for in gives ALL key/value pairs
console.log("for (let index in normalArray)");
for (let index in normalArray)
console.log(`${index}: ${normalArray[index]}`);
/*
0: 11
1: 23
2: 35
3: 48
-1: 5
foo: bar
*/
// It counts only positive index
console.log("normalArray.length");
console.log(normalArray.length);
/* 4 */
Problem with arrays

Fine.. I will be careful while using arrays. Great! But what if you are writing a module/lib and you have to pass you array to the caller? What if she/he adds non-positive index?

Thankfully, we have a new type of object in JavaScript – Proxy. It does what its name says – Proxy for other objects. A Proxy can “intercept” access to another object

Let’s build a “Pure” Array

First, let’s understand Proxy

// Simple object
let person = {
id: 0,
name: "default",
}
// There is no validation if object is used directly
person.id = "Eich"; // We would like it to be positive integer
person.name = 3.14; // Name should ideally be non blank string
console.log(person);
// Let's setup Proxy handler with validations
const handler = {
set(object, property, value, receiver) {
// id should be positive integer
if (property == "id") {
if (typeof value != 'number' || value < 0)
throw "id should be a positive number";
}
// name should be a non blank string
if (property == "name") {
if (typeof value != 'string' || value.trim() == "")
throw "name should be a non blank string";
}
// Passed all validations, so assign to real object
return Reflect.set(object, property, value, receiver);
}
}
// Use a Proxy to person object
const PersonProxy = new Proxy(person, handler);
// These should give errors
PersonProxy.id = "Brendan";
PersonProxy.name = 1;
console.log(PersonProxy);
view raw SimpleProxy.js hosted with ❤ by GitHub
Simple Proxy

As we can see, Proxy can “trap” any access to the object and change the behaviour. In the case above, we are able to validate that id should be integer and name should be a string. We can build a similar Proxy for array and make sure that the index is only positive integers.

Pure Array

// Array proxy
const PureArray = (args) =>
new Proxy([args], // Create internal array
// Handler with traps
{
// Intercept assignment to array using index
set(target, property, value, receiver) {
// Allow built in properties like length
if (Reflect.has(target, property))
return Reflect.get(target, property, receiver);
// Check if property is positive integer
const index = Number.parseInt(property);
if (Number.isNaN(index) || index < 0)
throw new Error("Index must be >= 0");
// Otherwise call the internal array
return Reflect.set(target, property, value, receiver);
},
// Intercept access to the array via index
get(target, property, receiver) {
// Check if its one of the built in properties
if (Reflect.has(target, property))
return Reflect.get(target, property, receiver);
// Check if property is positive integer
const index = Number.parseInt(property);
if (Number.isNaN(index) || index < 0)
throw new Error("Index must be >= 0");
// Otherwise call the internal array
return Reflect.get(target, property, receiver);
}
}
);
// let's create our "Pure" array
const myArray = PureArray(1, 2, 3);
console.log(myArray);
// Normal operations work
myArray[3] = 4;
console.log(myArray);
console.log(myArray.slice(2, 3));
console.log(myArray.length);
// These don't work anymore
myArray[1] = 5;
console.log(myArray[3]);
view raw PureArray.js hosted with ❤ by GitHub
Pure Array

There we have it! We have used a Proxy object to intercept access to an array and allow ONLY non-positive index. For others, it will appear to be an Array and they would see no difference. All array methods will continue to work but we will ensure that only positive indexes are allows.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s