4

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.

Md Sabbir Alam
  • 4,937
  • 3
  • 15
  • 30
  • Does this answer your question? [adding custom functions into Array.prototype](https://stackoverflow.com/questions/948358/adding-custom-functions-into-array-prototype) – pilchard Nov 03 '20 at 16:30
  • Not really. I was looking for some way to customize the array object during each update and during the creation of the array. – Md Sabbir Alam Nov 03 '20 at 16:33
  • "*I know how to add a custom property to an object*" - can you show what you mean by that? Maybe some code for how you get a custom method to run every time a non-array object is created or data is stored in it? – Bergi Nov 03 '20 at 16:39
  • Like I can do that to add the `foo` method to Array prototype and call it using array variables. `Array.prototype.foo = foo;` . And then if I create an array called `bar` and run `bar.foo()` it will execute my custom foo method. But I don't know how to run the `foo` method everytime an array is created and everytime an array is updated. – Md Sabbir Alam Nov 03 '20 at 16:42
  • maybe this could be useful? https://stackoverflow.com/questions/5100376/how-to-watch-for-array-changes – John Nov 03 '20 at 16:45
  • 2
    @sabbir.alam You can't do that, it is not possible to hijack "every array". You can explicitly wrap some specific arrays though, so that you control how they are updated, and during that update also do whatever else you want. – Bergi Nov 03 '20 at 16:51
  • Sounds like you need a listener on the object? Perhaps [add listener to javascript object](https://stackoverflow.com/questions/12059676/adding-listener-functions-to-a-javascript-object) – ATD Nov 03 '20 at 16:57
  • @ATD, Is that possible to add listener for existing objects like `Array` in JS? According to @Bergi's suggestions, it is impossible to do that. – Md Sabbir Alam Nov 03 '20 at 17:01
  • I've never done it myself, so I can't be sure. I would think that adding a listener using Array.prototype would achieve the same result as in those examples as the listener is being added to the Car.prototype. There are plenty of examples on here about adding things to Array.prototype, so I don't see that this would be any different. – ATD Nov 03 '20 at 17:08
  • Depends on what the `customMethod` does. If it as simple as computing the max value, you could add a getter to the `Array.prototype`. Example: https://jsfiddle.net/adigas/37axrngL/ – adiga Nov 03 '20 at 17:09
  • @adiga, actually using your solution, if I call the myArray.max it will call the get(). So everytime the max will be computed. I want the max to be computed only when the array is created/updated. Then everytime I try to access the max value I will get that value without computing again. – Md Sabbir Alam Nov 03 '20 at 17:13
  • If you want to listen to changes, you need to use [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy). Or create another class which `extends Array` and create instances of that and use Proxy in it to listen to changes. It's hard to use native `Array.prototype` without using a getter – adiga Nov 03 '20 at 17:17

3 Answers3

2

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!)
customcommander
  • 17,580
  • 5
  • 58
  • 84
  • You mean proxy is for individual array instances, right? – Md Sabbir Alam Nov 03 '20 at 18:34
  • @sabbir.alam Yes. But I'm sure we could write a proxy that return another proxy. – customcommander Nov 03 '20 at 18:41
  • Now, this is going straight above my head. Some pointer for understanding proxy would be nice where I can see this pattern of returning a new proxy. – Md Sabbir Alam Nov 03 '20 at 18:43
  • An offtopic question, Why everyone is double quoting hijack like this `"hijack"`? :) – Md Sabbir Alam Nov 03 '20 at 18:46
  • @sabbir.alam I was just trying to say that you can't automatically intercept all the calls just like that for free. (I didn't mean this in a bad way though :) – customcommander Nov 03 '20 at 18:51
  • No, It is alright. :) I was just curious. Anyway, could you please help me with giving some pointer where I can learn the pattern you have just mentioned? About returning a proxy from another proxy? – Md Sabbir Alam Nov 03 '20 at 18:53
  • @sabbir.alam Actually I asked [this question](https://stackoverflow.com/q/59003675/1244884) a while ago where I make use of a proxy-returning proxy. Hope this helps. – customcommander Nov 03 '20 at 18:57
0

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.

wordragon
  • 1,297
  • 9
  • 16
0

You have the right idea here:

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

You only need to change the Array class from which you are extending your class, It should be Array<T> instead of just Array like this:

class MyCustomArray extends Array<T> {
   constructor(...items) {
      super(...items);
      console.log('custom array');
   }
}

Here <T> the T represents the type of objects contained in the array, for example if it will be an array of just numbers then you would put <number>.

Now you can add any functionality to this array and create instances of this array using this sintax:

const array = new MyCustomArray(item1, item2...)

Hope this helps