enbyte
enbyte

Reputation: 11

How can I proxy [[set]] on all JavaScript array objects?

I'm trying to proxy the [[set]] method on JavaScript arrays. Is it possible to proxy all arrays, and not just a specific one? My handler would check the object being set in the array and if it hasOwnProperty('xyz') then console.log it.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy says that I would do that with something like

set(target, prop, val) { // to intercept property writing
    if (val.hasOwnProperty('xyz') {
        console.log(prop);
    };
    target[prop] = val;
    return true;
  }

However, Proxy seems to only work on single objects. Given that there is no prototype [[set]], how can I proxy this operation for every array?

Ideally, it would work something like this:

Array = new Proxy(Array, {
set(target, prop, val) { // to intercept property writing
    if (val.hasOwnProperty('xyz') {
        console.log(prop);
    };
    target[prop] = val;
    return true;
  }
});

let obj = {xyz: 1}
let list = [];
list[0] = obj; // would log 1

I tried the above code snippet, but it does not redefine the method for every array. One possible method could be to proxy Array.prototype.constructor to return an array with the [[set]] pre-proxied to have the check for the value.

Thanks!

Upvotes: 0

Views: 73

Answers (1)

KLASANGUI
KLASANGUI

Reputation: 1144

This code actually does not replace all Arrays... You can't actually do that. Also, when you cast [] then JavaScript will always use the native Array over the Array defined in your context...

There's another bad-side in putting your Proxy over your array prototype because it will actually be under the actual array object. And you need it to be over your object, not under it.

Thus, the only solution is to make a function like the above that will construct a fully functional proxied array.

/* Keep `Array` as is. It's better this way. */
function MyHandledArray ()
{
   /* Your proxy should go over your object, not it's prototype. */
   return new Proxy
   (
      /* This just use the `Array` constructor to build a new object. */
      Reflect.construct(Array, arguments, MyHandledArray),
      /* Bellow is the handler that will intercept the new object. */
      {
         set: ( Target, Property, Value ) =>
         {
            if ( ( typeof Value == 'object' ) && Reflect.has(Value, 'xyz') )
            {
               /* You want to log the indexes which contains objects with xyz property? */
               // console.log(Property);
               /* Or you wanted to log the `xyz` property value of the `Value` object? */
               console.log(Value.xyz);
            }
            
            if ( typeof Target == 'object' )
            {
               Target[Property] = Value;
            
               return Reflect.has(Target, Property);
            }
            
            return false;
         }
      }
   );
}

/* Adjust the function to properly inherit `Array`... */
Reflect.setPrototypeOf(MyHandledArray, Array);
Reflect.setPrototypeOf(MyHandledArray.prototype, Array.prototype);

/* Then, only then, use it. */

var Obj = {xyz: 1};
var Arr = MyHandledArray();

Arr[0] = Obj; // outputs: 1
Arr[1] = {xyz: 2}; // outputs: 2
Arr[3] = {nothing: null}; // outputs nothing
Arr[4] = {xyz: 'Hello', more: 'stuff'}; // outputs: Hello
/* It will work in all inserting methods too. */
Arr.push({xyz: 'World', more: 'stuff'}); // outputs: World

/* Note that the defined proxy does not verify the contents set through constructions... */
var NewArr1 = MyHandledArray({xyz: 'This is not logged'});
var NewArr2 = MyHandledArray.from(Arr);


NOTE: Neither your question or your example required to verify the construction parameters... You can just add the following at the start of the function if needed:

for ( const Argument of arguments )
{
   if ( ( typeof Argument == 'object' ) && Reflect.has(Argument, 'xyz') )
   {
      console.log(Argument.xyz);
   }
}  

Upvotes: -1

Related Questions