29

I'm trying to use Javascript Proxy objects to trap the arguments that are passed to a 'method' of the target that I'm proxying.

Please consider this example:

var test = {
    doSomething: function() {
        console.log( arguments.length );
    }
};

var testProxy = new Proxy( test, {
    get: function( target, property, receiver ) {

        // I'd like to have access to any arguments when
        // the property being accessed here is a function
        // that is being called

        return target[ property ];
    }
} );

testProxy.doSomething( 'this', 'is', 'lame' ); // I want to trap those arguments

It appears that these Proxy objects only allow you to trap accessing the property, but not the actual function call, with its arguments, when the property is in fact a function.

After reflecting a bit on the matter, I "get" (pardon the pun) that the get method is just intended for property access, in stead of invocation, but then I would have expected to be able to define something like a call method in the Proxy as well.

Perhaps it's doable with defining an apply method in the Proxy, but then I'd probably have to create a Proxy object for each individual method of the object I want to proxy; and that's not what I am after.

Unless I'm overlooking an actual alternative possibility here: how is it that this is overlooked in the Proxy implementation?! Isn't the whole point of a proxy to be able to intercept method calls and their arguments as well?

Or is this yet another misunderstanding of Javascript, on my part, about Javascript not being a 'classical' OOP language, and that the functionality I'm looking for wouldn't actually make sense in the context of Javascript?

user3840170
  • 26,597
  • 4
  • 30
  • 62
Decent Dabbler
  • 22,532
  • 8
  • 74
  • 106
  • I just realized that there probably *is* a way to achieve what I want; I'll answer my own question if the test I have in mind succeeds. – Decent Dabbler Jul 31 '14 at 21:32

4 Answers4

45

There actually is a way to do this, of course! I just hadn't thought it through thoroughly enough. I can just return a 'proxy' function and trap the arguments in there:

var test = {
    doSomething: function() {
        console.log( arguments.length );
    }
};

var testProxy = new Proxy( test, {
    get: function( target, property, receiver ) {

        switch( property ) {
            case 'doSomething':
              // you just have to return a proxy function
              return function() {
                  // arguments accessible, after all!
                  console.log( 'testProxy::doSomething() arguments.length: ' + arguments.length );

                  // here you can still invoke the original method, of course
                  target[ property ].apply( this, arguments );
              }
            break
        }

        return target[ property ];
    }
} );

testProxy.doSomething( 'this', 'is', 'not', 'so', 'lame', 'after', 'all' );
Decent Dabbler
  • 22,532
  • 8
  • 74
  • 106
  • 1
    Is there any way to capture the context of the method call as well? I tried the same but the "this" in "target[ property ].apply( this, arguments )" isn't the correct context if your method references "this" in itself... EDIT: you can pass a reference to the actual target, in your case "target[ property ].apply( test, arguments )" – mayacoda Nov 02 '17 at 13:49
  • 6
    @mayacoda I think the correct is: `target[ property ].apply( target, arguments );` – Marinos An Jul 18 '18 at 15:45
  • Man! You are my savior! Thanks for sharing this. I was literally stuck with this for days. – ganjim Aug 27 '21 at 20:08
  • Ok, i got it working, but there's a catch, the `return function()` statement is very important, if i do `return () =>` it won't work. Thanks. – windmaomao Nov 06 '21 at 15:08
  • The problem I see with this solution is that inside the proxy function if we are calling a function that works differently depending on the context (e.g. `console.log()` when we want the file and line of the CALLER, and not the ones INSIDE the function), the context is lost (again, e.g. `console.log()` would tell the file an line of the 'target[property].apply...` line. I have not found any solution to this. – Raúl Núñez de Arenas Coronado Feb 09 '23 at 20:03
4

another snippet : )

const obj_hidden = {};

const obj = new Proxy(obj_hidden, {
    get(target, prop) {
        if (typeof target[prop] == 'function') {
          return function (...args) {
            console.dir({ call: [prop, ...args] });
            return target[prop].apply(target, args);
          }
        }
        console.dir({ get: prop });
        return target[prop];
    },
    set(target, prop, value) {
        console.dir({ set: [prop, value] });
        target[prop] = value;
        return true;
    }
});
milahu
  • 2,447
  • 1
  • 18
  • 25
  • Makes no sense doing `...args` when the language already is giving it to you via `arguments` when using a "regular" function and not an arrow function. Upvoted as a good answer. – vsync Feb 05 '22 at 14:38
  • @vsync problem with `arguments` is 1) its obscure and 2) its an object, not an array – milahu Feb 05 '22 at 15:59
  • Doesn't matter for this example. Been using `arguments` for 20 years almost, no problems. if you need to convert it to array simple do `[...arguments]` – vsync Feb 05 '22 at 21:00
1

Here's another snippet

var test = {
    doSomething: function() {
        console.log( arguments.length );
    }
};

var testProxy = new Proxy( test, {
    get: function( target, property, receiver ) {

          // to have access to any arguments
    
          return ( ...args ) => target[property].apply(target, args);
          
      }
});

const value = testProxy.doSomething( 'this', 'is', 'lame' ); // those arguments will be trapped
Giuseppe Canale
  • 470
  • 7
  • 15
0

Thanks for sharing your answer. It helped me figure out how to fix my problem, which is fairly similar to this one. I figured I share mine as well, maybe it will be helpful.

I intended to wrap the arguments of the callback function passed to promise objects(the resolve and reject function when creating a new promise). So I created a proxy for Promise object to modify the constructor, but in the constructor, I couldn't access the arguments of the first argument of the promise constructor. This is how I did it, thanks to Decent's answer

// Wrap promise:
let promiseWrapperHandlers = {
  construct: function(target, args) {
    let originalCb = args[0]
    if (typeof args[0] === 'function') {

      let wrappedCb = function() {
        let resFn = arguments[0] || (() => {})
        let wrappedResolve = function(v) {
          console.log("resolving promise with " + v);
          return resFn(v);
        }

        let rejFn = arguments[1] || (() => {})
        let wrappedReject = function(err) {
          console.log("rejecting promise with " + err);
          return rejFn(err);
        }

        return originalCb(wrappedResolve, wrappedReject)
      }
      args[0] = wrappedCb
    }


    let p = new target(...args)
    return p
  },
}

const RealPromise = Promise
Promise = new Proxy(RealPromise, promiseWrapperHandlers)
// END wrap promise

const p = new Promise((resolve, reject) => {
  resolve(122)
})
const p2 = new Promise((resolve, reject) => {
  reject(121)
})
ganjim
  • 1,234
  • 1
  • 16
  • 30