2

I was looking around online and I saw someone do something similar, Array.prototype.push was proxied and every push was handled, I want to do something like this:

new Proxy(Array.prototype.push, handler);

const array = [];

array.push("e"); // logs "e" to the console somehow from the handler

How can this be achieved?

Sebastian Kaczmarek
  • 8,120
  • 4
  • 20
  • 38
bread
  • 160
  • 1
  • 10
  • 1
    Are you sure you need to use a proxy?, are you wanting everywhere you you create an array and call push you get logged, if so you just override the prototype, ps. Be careful doing this though, altering prototypes is a little bit frowned on. – Keith Mar 01 '23 at 14:56
  • 1
    This sounds like [an XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). What do you want to achieve which requires proxying a built-in method? – VLAZ Mar 01 '23 at 14:56
  • @VLAZ I'm making a script that needs to be injected into a website, I'm trying to access an object that has certain attributes, I'm trying to proxy Array.push so I can do checks on each push and see if the push was done on the object I'm looking for, and if it was, save the object. – bread Mar 01 '23 at 14:58
  • 1
    You can't use a proxy to intercept existing arrays, proxies work by wrapping an existing object and then all access is done via the proxy. Like I mentioned in first comment you can override the array protototype, but be very very careful when doing this. – Keith Mar 01 '23 at 15:00
  • @Keith makes sense, how would I alter `Array.prototype.push` and log the pushed elements, while also carrying out the original push? – bread Mar 01 '23 at 15:05
  • 1
    @Keith is right. The only way to somehow get things to work as described is to overwrite the built-in global array. Which is usually to be avoided. Even if you were to modify it, at that point it wouldn't matter whether you used a proxy or not as you're already intrusively changing the built-in global. Proxies are transparent only when you get a chance to hand over a proxy instead of the object. Then it's the same to the consumer. But making sure all consumers use a proxy of an object you don't control is not that. And when built-ins are involved you best thread carefully. – VLAZ Mar 01 '23 at 15:05

4 Answers4

4

This will work:

const handler = {
  apply(target, thisArg, argumentsList) {
    console.log(`called push with argument:', ${argumentsList}`);
    return Reflect.apply(target, thisArg, argumentsList)
  }
};

Array.prototype.push = new Proxy(Array.prototype.push, handler);


const a = []

a.push(1)

More info:

MDN article about Proxy

handler.apply

But it can break things so be careful

Konrad
  • 21,590
  • 4
  • 28
  • 64
  • 1
    Using a proxy here is pointless, if your going to override the prototype, then do it there, the proxy has gained nothing. – Keith Mar 01 '23 at 15:05
  • @Keith I tried to do that without a proxy and it caused issues, but maybe I did something wrong – Konrad Mar 01 '23 at 15:10
  • 1
    You just need to remember the old method, -> `const old = Array.prototype.push; Array.prototype.push = function (...args) { console.log(...args); old.call(this, ...args);}` – Keith Mar 01 '23 at 15:15
  • This is the sort of thing I was looking for, appreciate it. – bread Mar 01 '23 at 15:16
  • @bread Yes, but just remove the proxy, it's not doing anything, apart from making the push slower. – Keith Mar 01 '23 at 15:22
  • Oops just noticed, in my example, forgot the return. `old.call` -> `return old.call`, this is why you need to be extra careful overriding built ins.. :) – Keith Mar 01 '23 at 15:37
  • @Keith please add your example as a separate answer, maybe someone will be looking for this under this question in the future :) – Konrad Mar 01 '23 at 15:45
2

You need to proxy the array and call methods from the proxy.

// Based on:
// - https://stackoverflow.com/a/35610685/1762224
// - https://stackoverflow.com/a/47911346/1762224
const
  arr = [],
  traceMethods = ['push', 'unshift'],
  proxiedArr = new Proxy(arr, {
    get(target, prop, receiver) {
      if (traceMethods.includes(prop)) {
        return function(item) {
          console.log(`${prop}ing ${item}`);
          return target[prop](...arguments);
        }
      }
      return target[prop];
    },
  });

proxiedArr.push('e'); // logs 'pushing e' to the console
proxiedArr.unshift('f'); // logs 'unshifting f' to the console

console.log(arr); // ["f", "e"]
.as-console-wrapper { top: 0; max-height: 100% !important; }
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
  • Yeah, but the OP is wanting to intercept existing arrays, – Keith Mar 01 '23 at 15:28
  • 1
    @Keith noted, this is a little more cumbersome, but a bit safer. It does not override the default prototype method, although Konrad's shows that you can achieve this with the use of `apply()` and `Reflect`. – Mr. Polywhirl Mar 01 '23 at 15:31
2

Capturing push method on all arrays on a webpage can easily be done by overriding the prototype method.

But, a big warning, doing this could potentially cause side effect. In fact a long time ago in Javascript land they used to be a lib called prototype that actually did this, and got a lot of flack for doing it.

Saying that, there is one area were overriding built-in is recommended, and that's pollyfilling new ES features.

If all your wanting to do is log into the console, maybe for debugging purposes, then your maybe OK.

So with all the warnings out the way, here is an example.

//keep a reference to the old push method
const oldPush = Array.prototype.push;
//now implement our replacement.
Array.prototype.push = function (...args) {
  console.log(...args);
  return oldPush.call(this, ...args);
}

//lets test.
const a = []
a.push(1)
a.push(1,2,3);
a.push('hello','there');
Keith
  • 22,005
  • 2
  • 27
  • 44
  • 1
    "*a long time ago in Javascript land they used to be a lib called prototype that actually did this, and got a lot of flack for doing it.*" at the same ancient times, there was also a lib called MooTools that also did that. It caught some flak. Especially [when it broke adding `contains` method to arrays by defining a `contains` itself](https://esdiscuss.org/topic/having-a-non-enumerable-array-prototype-contains-may-not-be-web-compatible). The method was then renamed to `includes()` (and the string one to match) which meant the library literally changed the web. Not necessarily for the better. – VLAZ Mar 02 '23 at 07:27
0

Of course, you could also do this without the Proxy for a solution that doesn't change how Arrays work for the rest of the code (which can produce the kind of issue that is hard to troubleshoot):

function push (arr, ...args) {
  console.log('Pushed called with', ...args)
  arr.push(...args)
}

const arr = []

push(arr, 1)
console.log('arr', arr)
Shawn
  • 10,931
  • 18
  • 81
  • 126