Javascript 使用代理捕获所有链接的方法和getter(用于延迟执行) 背景:

Javascript 使用代理捕获所有链接的方法和getter(用于延迟执行) 背景:,javascript,ecmascript-6,Javascript,Ecmascript 6,假设我有一个对象,obj,带有一些方法和一些getter: var obj = { method1: function(a) { /*...*/ }, method2: function(a, b) { /*...*/ }, } Object.defineProperty(obj, "getter1", {get:function() { /*...*/ }}); Object.defineProperty(obj, "getter2", {get:function() { /*

假设我有一个对象,
obj
,带有一些方法和一些getter:

var obj = {
    method1: function(a) { /*...*/ },
    method2: function(a, b) { /*...*/ },
}
Object.defineProperty(obj, "getter1", {get:function() { /*...*/ }});
Object.defineProperty(obj, "getter2", {get:function() { /*...*/ }});
obj
是可链接的,链将定期包括方法和getter:
obj.method2(a,b).getter1.method1(a).getter2
(例如)

我知道getter的这种链式用法有点奇怪,在大多数情况下可能是不可取的,但这不是一个常规的js应用程序(它是针对一个应用程序)

但是,如果(出于某种原因)我们真的想懒洋洋地执行这些链式方法/getter呢?比如,只在调用某个“最终”getter/方法时执行它们

obj.method2(a,b).getter1.method1(a).getter2.execute
在我的例子中,这个“final”方法是
toString
,用户可以显式地调用它,也可以在尝试将它连接到字符串时隐式地调用它(
valueOf
也会触发求值)。但我们将使用
execute
getter示例来概括这个问题,并希望对其他人有用


问题: 所以这里的想法是:代理
obj
,只需将所有getter调用和方法调用(及其参数)存储在一个数组中。然后,在代理上调用
execute
时,以正确的顺序将所有存储的getter/method调用应用于原始对象并返回结果:

var p = new Proxy(obj, {
    capturedCalls: [],
    get: function(target, property, receiver) {
        if(property === "execute") {
            let result = target;
            for(let call of this.capturedCalls) {
                if(call.type === "getter") {
                    result = result[call.name]
                } else if(call.type === "method") {
                    result = result[call.name](call.args)
                }
            }
            return result;
        } else {
            let desc = Object.getOwnPropertyDescriptor(target, property);
            if(desc.value && typeof desc.value === 'function') {
                this.capturedCalls.push({type:"method", name:property, args:[/* how do I get these? */]});
                return receiver;
            } else {
                this.capturedCalls.push({type:"getter", name:property})
                return receiver;
            }
        }
    },
});
正如您所见,我了解如何捕获getter和方法的名称,但不知道如何获取方法的参数。我知道陷阱,但不太确定如何使用它,因为据我所知,它只适用于实际连接到函数对象的代理。如果专业人士能为我指出正确的方向,我将不胜感激。谢谢



似乎也有类似的目标。

我就快到了!我假设有一些特殊的处理方法,因此导致我陷入了
apply
陷阱和其他干扰,但事实证明,你可以用
get
陷阱做任何事情:

var obj = {
    counter: 0,
    method1: function(a) { this.counter += a; return this; },
    method2: function(a, b) { this.counter += a*b; return this; },
};
Object.defineProperty(obj, "getter1", {get:function() { this.counter += 7; return this; }});
Object.defineProperty(obj, "getter2", {get:function() { this.counter += 13; return this; }});

var p = new Proxy(obj, {
    capturedCalls: [],
    get: function(target, property, receiver) {
        if(property === "execute") {
            let result = target;
            for(let call of this.capturedCalls) {
                if(call.type === "getter") {
                    result = result[call.name]
                } else if(call.type === "method") {
                    result = result[call.name].apply(target, call.args)
                }
            }
            return result;
        } else {
            let desc = Object.getOwnPropertyDescriptor(target, property);
            if(desc.value && typeof desc.value === 'function') {
                let callDesc = {type:"method", name:property, args:null};
                this.capturedCalls.push(callDesc);
                return function(...args) { callDesc.args = args; return receiver; };
            } else {
                this.capturedCalls.push({type:"getter", name:property})
                return receiver;
            }
        }
    },
});
返回函数(…args){callDesc.args=args;返回接收器;}位是魔法发生的地方。当他们调用一个函数时,我们返回一个“伪函数”,它捕获他们的参数,然后像普通函数一样返回代理。此解决方案可以使用
p.getter1.method2(1,2).execute
(使用
obj.counter==9
)等命令进行测试

这似乎工作得很好,但我仍在测试它,如果有任何问题需要解决,我会更新这个答案

注意:使用这种“惰性链接”方法,您必须在每次访问
obj
时创建一个新的代理。我只需将
obj
包装在一个“根”代理中,并在访问上述代理的一个属性时生成该代理即可

改进版: 这可能对世界上除了我以外的所有人都没用,但我想我会把它贴在这里以防万一。以前的版本只能处理返回
this
的方法。此版本修复了这一问题,并使其更接近于“通用”解决方案,用于记录链并仅在需要时懒洋洋地执行它们:

var fn = function(){};

var obj = {
    counter: 0,
    method1: function(a) { this.counter += a; return this; },
    method2: function(a, b) { this.counter += a*b; return this; },
    [Symbol.toPrimitive]: function(hint) { console.log(hint); return this.counter; }
};
Object.defineProperty(obj, "getter1", {get:function() { this.counter += 7; return this; }});
Object.defineProperty(obj, "getter2", {get:function() { this.counter += 13; return this; }});

  let fn = function(){};
  fn.obj = obj;
  let rootProxy = new Proxy(fn, {
      capturedCalls: [],
      executionProperties: [
        "toString",
        "valueOf",
        Symbol.hasInstance,
        Symbol.isConcatSpreadable,
        Symbol.iterator,
        Symbol.match,
        Symbol.prototype,
        Symbol.replace,
        Symbol.search,
        Symbol.species,
        Symbol.split,
        Symbol.toPrimitive,
        Symbol.toStringTag,
        Symbol.unscopables,
        Symbol.for,
        Symbol.keyFor
      ],
      executeChain: function(target, calls) {
        let result = target.obj;

        if(this.capturedCalls.length === 0) {
          return target.obj;
        }

        let lastResult, secondLastResult;
        for(let i = 0; i < capturedCalls.length; i++) {
          let call = capturedCalls[i];

          secondLastResult = lastResult; // needed for `apply` (since LAST result is the actual function, and not the object/thing that it's being being called from)
          lastResult = result;

          if(call.type === "get") {
            result = result[call.name];
          } else if(call.type === "apply") {
            // in my case the `this` variable should be the thing that the method is being called from
            // (this is done by default with getters)
            result = result.apply(secondLastResult, call.args);
          }

          // Remember that `result` could be a Proxy
          // If it IS a proxy, we want to append this proxy's capturedCalls array to the new one and execute it
          if(result.___isProxy) {
            leftOverCalls = capturedCalls.slice(i+1);
            let allCalls = [...result.___proxyHandler.capturedCalls, ...leftOverCalls];
            return this.executeChain(result.___proxyTarget, allCalls);
          }

        }
        return result;
      },
      get: function(target, property, receiver) {

        //console.log("getting:",property)

        if(property === "___isProxy") { return true; }
        if(property === "___proxyTarget") { return target; }
        if(property === "___proxyHandler") { return this; }

        if(this.executionProperties.includes(property)) {

          let result = this.executeChain(target, this.capturedCalls);

          let finalResult = result[property];
          if(typeof finalResult === 'function') {
                finalResult = finalResult.bind(result);
          }
          return finalResult;

        } else {
            // need to return new proxy
            let newHandler = {};
            Object.assign(newHandler, this);
            newHandler.capturedCalls = this.capturedCalls.slice(0);
            newHandler.capturedCalls.push({type:"get", name:property});
            let np = new Proxy(target, newHandler)
            return np;
        }
      },
      apply: function(target, thisArg, args) {
          // return a new proxy:
          let newHandler = {};
          Object.assign(newHandler, this);
          newHandler.capturedCalls = this.capturedCalls.slice(0);
          // add arguments to last call that was captured
          newHandler.capturedCalls.push({type:"apply", args});
          let np = new Proxy(target, newHandler);
          return np;
      },
      isExtensible: function(target) { return Object.isExtensible(this.executeChain(target)); },
      preventExtensions: function(target) { return Object.preventExtensions(this.executeChain(target)); },
      getOwnPropertyDescriptor: function(target, prop) { return Object.getOwnPropertyDescriptor(this.executeChain(target), prop); },
      defineProperty: function(target, property, descriptor) { return Object.defineProperty(this.executeChain(target), property, descriptor); },
      has: function(target, prop) { return (prop in this.executeChain(target)); },
      set: function(target, property, value, receiver) { Object.defineProperty(this.executeChain(target), property, {value, writable:true, configurable:true}); return value; },
      deleteProperty: function(target, property) { return delete this.executeChain(target)[property]; },
      ownKeys: function(target) { return Reflect.ownKeys(this.executeChain(target)); }
  });
var fn=function(){};
var obj={
柜台:0,,
方法1:函数(a){this.counter+=a;返回this;},
方法2:函数(a,b){this.counter+=a*b;返回this;},
[Symbol.toPrimitive]:函数(提示){console.log(提示);返回this.counter;}
};
defineProperty(obj,“getter1”,{get:function(){this.counter+=7;返回this;}});
defineProperty(obj,“getter2”,{get:function(){this.counter+=13;返回this;}});
设fn=function(){};
fn.obj=obj;
设rootProxy=newproxy(fn{
capturedCalls:[],
executionProperties:[
“toString”,
“价值”,
Symbol.hasInstance,
Symbol.isconcat可扩展,
Symbol.iterator,
Symbol.match,
Symbol.prototype,
Symbol.replace,
Symbol.search,
符号。物种,
Symbol.split,
Symbol.toPrimitive,
Symbol.toStringTag,
Symbol.uncopables,
Symbol.for,
Symbol.keyFor
],
ExecuteCain:函数(目标,调用){
让结果=target.obj;
if(this.capturedCalls.length==0){
返回target.obj;
}
让lastResult,secondLastResult;
for(设i=0;i