Run custom method on array update/create in JavaScript

Viewed 473

I know how to add a custom property to an object.

Lets say I have a foo method,

const foo = () => { console.log('custom method'); }

I can add the foo method to the Array prototype and call it using array variables by

Array.prototype.foo = foo;

And then if I create an array called,

bar = [1, 2, 3];

and run

bar.foo()

It will execute my custom foo method. But I don't know how to run the foo method every time an array is created and every time an array is updated.

I want to run a custom method and store some data during array creation/updating in JavaScript. How do I do that?

Lets say I have a custom method,

const customMethod = () => {
   ...doing some stuff    
}

I want this custom method to run every time an array is created and store some data in that array. Like I want to find the maximum of the array and store it inside the array with key maximum as we are storing length now. So that after creating/updating the array I don't have to calculate the maximum. I can just call myArray.maximum and will get that maximum value.

This approach does something similar. But it requires to add event listener and a new push method so that it can fire an event everytime something pushed into the array using that custom push method. But in that case my custom function will not be updated if I use regular Array.prototype.push method or create a new array using spread operator like this, newArr = [...oldArray, value].

Update: After searching and with the help of the links in comment I found out that it is not possible to modify the Array object without extending it or creating a custom array type from scratch(which is not acceptable).

I have tried to extend the existing Array type and create MyCustomArray. But it doesn't work as an array at all.

class MyCustomArray extends Array {
   constructor(...args) {
      super(...args);
      console.log('custom array');
   }
}

Any idea how can I extend the Array to create my CustomArray type and add a listener to it, so that every time I create/update a CustomArray it calculates the max and set it as an array max attribute (With minimum code change)?

So my CustomArray will have all the methods and instance variables like a normal array. But it will do one extra thing, every time I update/create a CustomArray it will calculate the max in that array and set it as property.

2 Answers

I don't think you can "hijack" all arrays just like that. Even if you monkey patch the native Array function, arrays created with the literal notation i.e. [] won't be affected by that:

console.log(new Array(1,2,3));
console.log([4,5,6]);

Array = function () {
  throw new Error;
};

try {
  console.log(new Array(1,2,3));
} catch (e) {
  console.log('Cannot create array with new Array');
}

console.log([4,5,6]);

As you probably figured you will need to pass your arrays to something else. You just need to pick the solution that works for you. Personally I'd try with a proxy:

const spy =
  (arr, fn) =>
    new Proxy(arr, {
      get(target, prop) {
        if (prop === "max") {
          return Math.max(...target);
        }
        if (prop === "min") {
          return Math.min(...target);
        }
        if (typeof target[prop] === "function") {
          fn();
        }
        return target[prop];
      }
    });


const arr = spy([1,2,3], () => console.log('spied'));
arr.push(500);
arr.push(-10);

console.log(arr.min, arr.max, JSON.stringify(arr), Array.isArray(arr));

What I like about a proxy is that it can be designed so that it doesn't get in the way. In the example above arr is still an array. You can do all the normal stuff you used to do. For example Array.isArray still returns true and JSON.stringify still produces the expected result.

However be aware that in the implementation above, if you produce another array from arr it wouldn't be automatically proxied:

const arr = spy([1,2,3], fn);
arr.slice(1); // [2,3] (non-proxied array!)

Well, its a bit brute force, but, Array is kind of special in that if you provide a constructor to an extension class, its methods that create new arrays will use that constructor, so, for example, myCustomArray.filter would produce a myCustomArray object result rather than an Array object result. (If you DON'T provide a constructor, this doesn't work).

If I wanted to have ready access to a particular custom feature of an Array in that custom type, I'd probably null the value on creation, and extend methods that change the existing object (like pop) to null it as well, and then have a method which would either provide the non-null cached value, or calculate, cache and return the current value.

I didn't go through and count, but I think there are only a handful of array methods which change the existing arrays (as opposed to creating a new one), so it should not require much effort.

Related