当使用代理对象时,如何捕获目标方法的参数?

31

我正在尝试使用Javascript Proxy对象来捕获传递给我代理的目标的 'method' 的参数。

请考虑以下示例:

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

看起来这些Proxy对象只允许您捕获访问属性,而不是实际的函数调用及其参数,当属性实际上是一个函数时。

经过一番思考,我“明白了”(请原谅双关语),get方法只是用于属性访问,而不是调用,但是我本来希望能够定义类似于call方法的东西在Proxy中。

也许可以通过在Proxy中定义一个apply方法来实现,但那样我可能需要为要代理的对象的每个单独的方法创建一个Proxy对象;而这不是我想要的。

除非我忽略了实际的替代可能性:这在Proxy实现中是如何被忽视的?!代理的整个重点不就是能够拦截方法调用及其参数吗?

还是这又是我对Javascript的另一个误解,认为Javascript不是一个“经典”的面向对象编程语言,而我正在寻找的功能在Javascript的上下文中实际上没有意义?


我刚刚意识到可能有一种方法可以达到我想要的结果;如果我考虑中的测试成功了,我会回答自己的问题。 - Decent Dabbler
4个回答

46

当然,实际上是有方法做到这一点的!我只是没有彻底地思考过。我可以返回一个“代理”函数并在其中捕获参数:

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

1
有没有办法同时捕获方法调用的上下文?我尝试了同样的方法,但是在“target [property] .apply(this,arguments)”中的“this”不是正确的上下文,如果您的方法在自身中引用“this”...编辑:您可以传递对实际目标的引用,在您的情况下为“target [property] .apply(test,arguments)”。 - mayacoda
6
我认为正确的写法是:target[property].apply(target, arguments); - Marinos An
哇!你真是我的救星!感谢分享这个。我被这个问题困扰了好几天。 - ganjim
好的,我搞定了,但是有一个问题,return function()语句非常重要,如果我写成return () =>就不起作用。谢谢。 - windmaomao
我认为这个解决方案存在的问题是,在代理函数内部,如果我们调用一个根据上下文而有不同行为的函数(例如,当我们想要调用者的文件和行,而不是函数内部的文件和行时使用 console.log()),上下文就会丢失(同样地,例如 console.log() 会告诉我们 'target[property].apply...' 行的文件和行)。我还没有找到任何解决办法。 - Raúl Núñez de Arenas Coronado

5

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

当使用“常规”函数而不是箭头函数时,通过arguments语句,语言已经将...args提供给你了。所以使用...args没有任何意义。作为一个好的答案而被赞同。 - vsync
1
@vsync问题在于arguments的1)它很难理解,2)它是一个对象,而不是一个数组。 - milahu
这个例子并不重要。我已经使用了将近20年的 arguments,从来没有出过问题。如果您需要将其转换为数组,只需使用 [...arguments] 即可。 - vsync

1
这里是另一个片段。

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


0

感谢您分享您的答案。它帮助我解决了我的问题,这个问题与它相似。我想分享我的答案,也许会有所帮助。

我打算包装传递给 Promise 对象的回调函数的参数(创建新 Promise 时的 resolve 和 reject 函数)。因此,我为 Promise 对象创建了一个代理以修改构造函数,但在构造函数中,我无法访问 promise 构造函数的第一个参数的参数。这就是我如何做到的,感谢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)
})


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接