2

I have the following code, where I'm using Proxy object (proxy) to try to catch method calls and access of the properties:

Example: https://jsfiddle.net/r8j4fzxL/2/

(function() {
    'use strict';
    console.clear();

    //some empty class where I want to trap methods props
    class X {
        //...
    }

    let proxy = {
        get: function(target, prop, receiver) {

            console.log('get called: ',
                'target:', target,
                'prop:', prop,
                'receiver:', receiver
            );
            //this is OK, if we are called as a method.
            //but it isn't when called as .prop - because, obviously, we return a function here.
            return function(...args) {
                console.log('wrapper args:', args);
                return 42;
            }
        },


    };


    let p1 = new Proxy(X, proxy);
    //how to distinguish the two in the above proxy:
    console.log(p1.test('some arg passed'));
    console.log(p1.test);
})();

And I have two questions here.

  1. Generally, is this the right way, if I want to trap both properties access and method access? Or maybe should I go with .apply trap somehow (failed to do so though)?

  2. If this is the right way (using .get) - then how do I know how the user accessed the... thing? Via .foo; or via .foo();?

Resources I used and apparently didn't fully understand:

SO: JavaScript Equivalent Of PHP __call

Community
  • 1
  • 1
konrados
  • 1,047
  • 8
  • 21

3 Answers3

6

This is actually a solution for this question (which was marked as duplicate of this but is not !!): How to get function arguments in a Proxy handler

You cannot get the arguments in a "get" trap, because when the get trap is called the functions is not called yet!
But you can create another proxy with "apply" trap, here is the example:

(function() {
    'use strict';
    console.clear();

    //some empty class where I want to trap methods & props
    class X {

    }

    let proxy = {
        get: function(target, prop, receiver) {
            console.log(arguments);//this gives the arguments of the 'get' trap itself. 


        // normally you trap an existent funcion, in this example we are creating a new one here
        var F = function(...args){
            console.log('Original function call', args);
        }

        return new Proxy(F, {
                apply: function(target, thisArg, argumentsList) {
            // here you have the arguments
            console.log('Trapped function call', argumentsList);
                        return target.apply(thisArg, argumentsList);                        
                }});

        },


    };


    let p = new Proxy(X, proxy);
    console.log(p.test('some arg passed'));

})();

So the trick is to trap the function with get first, and instead of returning the original function, to return a Proxy with apply trap to the original function.

Enrique
  • 4,693
  • 5
  • 51
  • 71
  • ah, wow, that's pretty smart :) Thanks! And yeah, thanks for noticing that the other question isn't a duplicate :) – konrados Jul 03 '19 at 20:31
3

Proxies are essentially objects that expose a programmatic way to hook into the operations that objects can have performed on them. With that as a base, there is no way to distinguish a property access from a property access + call, at the point where the property is accessed. The fact that the returned value is a function is all that you can know.

From the standpoint of the language, p.foo() breaks down into

var p = ...
var foo = p.foo;
foo.apply(p, []);

as an example. p.foo wouldn't actually access .apply, but the point is that the property access is entirely independent of how the returned value is actually used/called.

So you option would essentially be to check whether the value of test was already a function, and if so, wrap that value with your wrapper. If it was not a function, it seems like you'll want to skip wrapping it. e.g.

get: function(target, prop, receiver) {
  var value = Reflect.get(target, prop, receiver);
  if (typeof value === "function") {
    return function(...args) {
      // probably call 'value' with whatever you need
    };
  } else {
    // return the origin value.
    return value;
  }
}
loganfsmyth
  • 156,129
  • 30
  • 331
  • 251
  • Thanks! I think I understand. But, just to understand it in 100% :) - if the *target* doesn't have the `test` at all, my mission is just... impossible? – konrados Apr 25 '18 at 17:24
  • 1
    If it doesn't have a value, you could hard-code it to be a function based on the name "test". You'd never want a property that was sometimes a function and sometimes not. The type of a property shouldn't change based on how the property is used, that would be super confusing. – loganfsmyth Apr 25 '18 at 18:04
1

Here's another snippet using get trap

const car = {
   color: 'blue',
   schema: "xlm"
}

const handler = {
   get(target, property) {
           console.log( property );
           console.log( target[property] ?? 'Not found' );
           return new Proxy ( ( ...args ) => {
              console.log( "args:" + args );
              return proxyObject;
           },
           { 
              get( _, innerProperty ){
                 console.log( "innerProperty", innerProperty);
                 return proxyObject[innerProperty];
              }
           })
         }
      }

let proxyObject = new Proxy(car, handler);
proxyObject.color.schema(8).not.be('red');
Giuseppe Canale
  • 470
  • 7
  • 15