2

Is it possible, given the following object

let target = {
 foo:0,
 result:[],
 bar(){
   //some code
 }
}

to then wrap said object in a Proxy()

let handler = {
  get(){
    // code here
  },
  apply(){
    // code here
   }
 }

 target = new Proxy(target,handler);

that catch the call to bar() and save the result to results:[] ?

I've given it a few tries

let target = {
    called:0,
    results:[],
    foo(bar){
        return bar;
    },
}

let handler = {
    get(target,prop){
        console.log('Get Ran')
        return target[prop];
    },
    apply(target,thisArg,args){
        console.log('Apply Ran')
        // never runs
    }
}

target = new Proxy(target,handler);
target.foo();

This code misses [[ apply ]] but catches the [[ get ]] (if memory serves, object method calls are done as two operations , [[ get ]] [[ apply ]])

let target = {
    called:0,
    results:[],
    foo(bar){
        return bar;
    },
}

let handler = {
    get(target,prop){
        return target[prop];
    },
    apply(target,thisArg,args){
        let product = target.apply(thisArg,args)
        return product;
    },
}

let prox = new Proxy(target.foo,handler);
    console.log(prox('hello'));

If I instead wrap the object method in a proxy it catches the [[ apply ]] but I lose the reference to the original object ( this ) and therefore lose access to the result array

I've also tried nesting the method proxy inside of the object proxy.

any thoughts ?

CodeSandBox of my actual code

// Documentation for Proxy

Proxy MDN

Javascript.info/proxy

// Other questions , about proxy , but not this use case

Understanding ES6 javascript proxies

user3840170
  • 26,597
  • 4
  • 30
  • 62
  • Looks like your target doesn't reference any of its own properties. So does it really need to have bindings to `this`? Also, unless you use the proxied version, you won't be able to listen in on function calls. Why not add extra functionality you want by wrapping the function in a function? Can you give a codesandbox example of what you are trying to achieve? – Daniel Duong Apr 29 '20 at 05:29
  • @DanielDuong Yep! i totally made one but neglected to add it to my original post To answer your question , As a for fun side project , I'm making a knock off version of Jest , to get a little more intuitive understanding of it , This is specifically for the Mock functionality, a mock function stores details about how its called, what the results of the calls where etc.. so thats where im coming from – Michael Richard Walker Apr 29 '20 at 05:34
  • @DanielDuong https://codesandbox.io/s/frosty-kowalevski-8y4co?file=/src/index.js – Michael Richard Walker Apr 29 '20 at 05:35

1 Answers1

1

According to spec

A Proxy exotic object only has a [[Call]] internal method if the initial value of its [[ProxyTarget]] internal slot is an object that has a [[Call]] internal method.

So below does catch the invocation if target is a function

(new Proxy(() => 'hello', { apply: () => console.log('catched') }))() // 'catched

Reciprocally, if target does not have a call method, then no proxying

try {
  (new Proxy({}, { apply: () => console.log('catched') }))() // throws
} catch (e){ console.log('e', e.message)}

So hint may be to proxy [get] then instead of returning the value (being the function bar, proxy that value to trap the eventual invocation

const p = new Proxy(
  { results: [], bar: () => { console.log('rywhite') } }, {
  get: (target, prop) => {
    if (prop !== 'bar') return target[prop]
    return new Proxy (target.bar, {
      apply () {
        target.results.push('what')
      }
    })
  }
})
p.bar // nothing bud'
p.bar(); console.log('res', p.results)
p.bar(); console.log('res', p.results)
p.bar(); console.log('res', p.results)

edit: NB: it is not necessary to create a new proxy every time

In code below, returning the same proxy is about twice faster

const N = 1e6
{
  const target = { results: 0, bar: () => { console.log('rywhite') } }
  const p = new Proxy(
    target, {
    get: (() => {
      const barProxy = new Proxy (target.bar, {
        apply () {
          target.results++
        }
      })
      return (target, prop) => {
        if (prop !== 'bar') return target[prop]
        return barProxy
      }
    })()
  })
  console.time('go')
  for (let i = 0; i < N; ++i) { p.bar() }
  console.timeEnd('go')
  console.log('res', p.results)
}
{
  const p = new Proxy(
    { results: 0, bar: () => { console.log('rywhite') } }, {
      get: (target, prop) => {
        if (prop !== 'bar') return target[prop]
        return new Proxy (target.bar, {
          apply () {
            target.results++
          }
        })
      }
    })
  console.time('neweverytime')
  for (let i = 0; i < N; ++i) { p.bar() }
  console.timeEnd('neweverytime')
  console.log('res', p.results)
}
grodzi
  • 5,633
  • 1
  • 15
  • 15