10

I am using Proxy to Proxy an object. The getter and setter work fine like expected. However, the apply method is never called.

    var p = new Proxy({}, {
    /* getter */
    get(target, name) {
        return target[name]
    },
    /* setter */
    set(target, name, value) {
        target[name] = value
    }, 
    /* supposedly called apply */
    apply(target,that,arg) {
        console.log('apply is called, what to do here?')        
    }
})

This way, I can assign something to p or return something even if it doesn't exist. When I for instance let the getter function return this

get(target, name) {
    return 'getting ' + name
},

and then console.log(p.flappy) I will get the response "getting flappy" even when it doesn't exist.

So far so good but when I try to call flappy doing p.flapppy() it wil throw an error that flappy is not a function.

This is still somewhat obvious because the getter does not return a function. When I let the getter return a function like this

get(target, name) {
    return function() { return 'this is '+name } 
},

I can call the property without it having to exist.

console.log(
    p.flappy() // this is flappy!
)

So when does apply get called? Not in the snippet I just showed here and also not in this case:

p.foo = function() {
    console.log('yay!')
    return 'foo!'
}

It does not work to do p.foo() or p.foo.call() or p.foo.apply(), in neither cases apply is called.

The ultimate purpose of this journey is that I want to return a DIFFERENT value depending on whether a property is being read or being called. Like this:

   p.someNewProperty // return what the getter function returns

    p.anotherProperty() // return something else here because this is a function call

Is this possible?

user3840170
  • 26,597
  • 4
  • 30
  • 62
Jochem Stoel
  • 1,371
  • 1
  • 13
  • 23

3 Answers3

12

I know this is question is a year old, but I ran into this as well and I found a way to do what you are trying to do. So this is for future reference, as I didn't find correct solutions elsewhere.

Short version: accessing functions inside an object (or a class) is essentially getting the property of the object that has the function. The trick is to return another Proxy with apply so you can proxy these functions correctly.

Consider the following object:

const myObject = {
  a: 'Hello world!',
  b: x => x * x
};

Accessing a or b shall both be caught by a Proxy's get, because they are properties of the object. You should catch all get and then filter for functions. Once you have a function, you return a new Proxy that catches this particular function with Proxy.apply. Then, to let the function execute as intended, inside the Proxy.apply we return a Reflect.apply, which calls the original function with the correct arguments as expected.

You will end up with this code:

const myProxyObject = new Proxy(myObject, {
  get(target, propKey, receiver) {    
    // Calling functions
    if (typeof target[propKey] === 'function') {
      return new Proxy(target[propKey], {
        apply(applyTarget, thisArg, args) {
          console.log(`Calling ${thisArg.constructor.name}.${propKey}(${args})`);
          return Reflect.apply(applyTarget, thisArg, args);
        }
      });
    }

    // Accessing properties
    if (target.hasOwnProperty(propKey)) {
      console.log(`Get value of ${target.constructor.name}.${propKey}`);
      console.log(`Value: ${target[propKey]}`);
    }

    return target[propKey];
  }
});

Demo on jsfiddle

You don't get the result of the function, because that would require you to execute it.

Note: it is possible to use this with classes and it works very nicely. The only caveat is that your Proxy will be catching all internal functions as well. In order to prevent logging dozens of valueOfs, I highly recommend to test if a function is native or not with something like this isNative function

7

As documented on MDN, the apply proxy method is for proxying a function call on the proxy object itself, not a call on a method of the object.

It only works with functions (as the Proxy target), not regular object instances, but here is how it would work:

var p = new Proxy(function() {}, {
    apply: function() {
        console.log('apply called');
    }
});
p();

The ultimate purpose of this journey is that I want to return a DIFFERENT value depending on whether a property is being read or being called.

It is not possible to directly do what you intend, nor would it really make sense. To call is to read the property.

Andrew
  • 5,839
  • 1
  • 51
  • 72
Alexander O'Mara
  • 58,688
  • 18
  • 163
  • 171
  • 1
    I'm sorry bud but this is not true. When you call p() in these examples it will simply return an error. "p is not a function" – Jochem Stoel Jun 05 '16 at 21:14
  • @Jochem I'm away from my desktop, but I'm reasonably certain the core of my answer is correct. Perhaps there is an implementation error in your code. What is the error you get? – Alexander O'Mara Jun 05 '16 at 21:17
  • I have literally copy pasted my code and before answering you I tried calling p() just to be sure that I am not wasting your time. – Jochem Stoel Jun 05 '16 at 21:18
  • 2
    @Jochem Aha! I mistakenly thought it would work with regular objects. Turns out it only works for functions, which makes sense. See my updated answer. – Alexander O'Mara Jun 05 '16 at 21:28
  • 1
    This tripped me up as well. The **target** of the Proxy has to be a function. – Andrew Jun 29 '20 at 17:47
0

and after some years...

yes, you can! you can return a DIFFERENT value depending on whether a property is being read or being called!

const authUser = { id: 1 }

const user = new Proxy(function () {}, {
    get (target, property) {
    return authUser.id
  },
  apply (target, thisArg, args) {
    // some stuff to get user
    return { id: args[0] }
  }
})

console.log(user.id)
console.log(user(2).id)

or you can use two step proxy.

const authUser = { id: 1 }

const user = new Proxy(function () {}, {
  get (target, property) {
    return userProxy(authUser.id, property)
  },
  apply (target, thisArg, args) {
    return userProxy(args[0])
  }
})

function userProxy(id, property) {
  // some stuff to get user
  const user = { id }

  return property ? user[property] : new Proxy(user, {
    get (target, property) {
      return user[property]
    }
  })
}

console.log(user.id)
console.log(user(2).id)
GraySe7en
  • 182
  • 2
  • 10