gpt4 book ai didi

javascript - 使用代理捕获所有链式方法和 getter(用于惰性执行)

转载 作者:数据小太阳 更新时间:2023-10-29 06:10:03 31 4
gpt4 key购买 nike

上下文:

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

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 应用程序(它用于 DSL )。

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

obj.method2(a,b).getter1.method1(a).getter2.execute

在我的例子中,这个“最终”方法是 toString,它可以由用户显式调用,或者当他们试图将它连接到字符串时隐式调用(valueOf 也会触发评估)。但我们将使用 execute getter 示例来扩大这个问题,并希望对其他人有用。


问题:

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

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陷阱,但我不太确定如何使用它,因为据我了解,它仅适用于实际附加到函数对象的代理。如果专业人士能在这里为我指明正确的方向,我将不胜感激。谢谢!


This question似乎有类似的目标。

最佳答案

我快到了!我假设有一些特殊的方法来处理方法,所以这让我陷入了 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 时创建一个新代理。为此,我只需将 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)); }
});

请注意,它代理了一个函数,因此它可以轻松捕获 apply。另请注意,链中的每一步都需要创建一个新的代理。它可能需要一些调整以适应与我的不完全相同的目的。同样,我不怀疑它在 DSL 构建和其他元编程之外毫无用处 - 我将它放在这里主要是为了给其他试图实现类似目标的人以灵感。

关于javascript - 使用代理捕获所有链式方法和 getter(用于惰性执行),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41886355/

31 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com