有没有办法使用 Javascript ES6 Proxy 来监视对象方法

Is there a way to use Javascript ES6 Proxy to spy on Object Methods

是否可能,给定以下对象

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

然后将所述对象包裹在 Proxy()

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

 target = new Proxy(target,handler);

捕获对 bar() 的调用并将结果保存到 results:[] ?

我试了几次

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();

这段代码错过了 [[ apply ]] 但抓住了 [[ get ]] (如果没记错的话,对象方法调用是作为两个操作完成的,[[ 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'));

如果我改为将对象方法包装在代理中,它会捕获 [[ apply ]] 但我丢失了对原始对象 ( this ) 的引用,因此无法访问结果数组

我也试过将方法代理嵌套在对象代理中。

有什么想法吗?

CodeSandBox of my actual code

// 代理文档

Proxy MDN

Javascript.info/proxy

// 其他问题,关于代理,但不是这个用例

根据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.

如果目标是 function

,那么下面确实会捕获调用

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

反过来,如果目标没有调用方法,则没有代理

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

因此提示可能是代理 [get] 而不是返回值(作为函数 bar,代理该值以捕获最终调用

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)


编辑:注意:没有必要每次都创建一个新代理

在下面的代码中,返回相同的代理大约快两倍

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)
}