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