问题相当简单。我们需要给函数注入一个参数,然后简单地从函数体中提取该参数。我将用打字稿呈现大纲。。。
abstract class Puzzle {
abstract assign(param, fn): any;
abstract getAssignedValue(): any;
async test() {
const wrapped = this.assign(222, async () => {
return 555 + this.getAssignedValue();
});
console.log("Expecting", await wrapped(), "to be", 777);
}
}
让我们设置场景:
- 假设严格模式,没有参数或被调用者。应该在最近的ish版本v8上运行得相当好
- 传递给
assign()
的函数必须是不接受任何参数的匿名箭头函数 - 。。。也是CCD_ 2。分配的值可以在调用期间存储在某个地方,但由于函数是
async
,并且可以有await
s,因此不能依赖于通过多个交错调用来保持值 this.getAssignedValue()
不接受任何参数,返回我们用assign()
方法分配的任何参数
如果能找到一个比我下面介绍的更优雅的解决方案,那就太好了。
编辑
好吧,我们似乎已经找到了一个受zone.js启发的好的固体解决方案。同样类型的问题也在那里得到了解决,解决方案是覆盖一些系统级原语的含义,比如SetTimeout和Promise。上面唯一令人头疼的是async语句,这意味着函数的主体可以有效地重新排序。异步最终是由Promise触发的,因此您必须使用上下文感知的东西来覆盖Promise。它非常复杂,因为我的用例在浏览器甚至节点之外,所以我不会用细节来烦你。对于大多数遇到这种问题的人,只需使用zone.js.
破解解决方案2
class HackySolution2 extends Puzzle {
assign(param: any, fn: AnyFunction): AnyFunction {
const sub = Object(this);
sub["getAssignedValue"] = () => param;
return function () { return eval(fn.toString()); }.call(sub);
}
getAssignedValue() {
return undefined;
}
}
在这个解决方案中,我正在创建一个覆盖getAssignedValue()
方法的对象,并重新评估传递函数的源代码,从而有效地更改了this
的含义。仍然不太符合生产级别。。。
编辑
哎呀,这打破了封闭。
我不知道typescript,所以这可能没有用,但像这样的东西呢
const build_assign_hooks = () => {
let assignment;
const get_value = () => assignment;
const assign = (param, fn) => {
assignment = param;
return fn;
}
return [assign, get_value];
};
class Puzzle {
constructor() {
const [assign, getAssignedValue] = build_assign_hooks();
this.assign = assign;
this.getAssignedValue = getAssignedValue;
}
async test() {
const wrapped = this.assign(222, async () => {
return 555 + this.getAssignedValue();
});
console.log("Expecting", await wrapped(), "to be", 777);
}
}
const puzzle = new Puzzle();
puzzle.test();
破解解决方案1
我们实际上有一个有效的实施。这是一个非常痛苦的破解,但证明了这应该是可能的。以某种方式也许有一个超级简单的解决方案,我错过了,只是因为我盯着这个看太久了。
class HackySolution extends Puzzle {
private readonly repo = {};
assign(param: any, fn) {
// code is a random field for repo. It must also be a valid JS fn name.
const code = 'd' + Math.floor(Math.random() * 1000001);
// Store the parameter with this code.
this.repo[code] = param;
// Create a function that has code as part of the name.
const name = `FN_TOKEN_${code}_END_TOKEN`;
const wrapper = new Function(`return function ${name}(){ return this(); }`)();
// Proceed with normal invocation, sending fn as the this argument.
return () => wrapper.call(fn);
}
getAssignedValue() {
// Comb through the stack trace for our FN_TOKEN / END_TOKEN pair, and extract the code.
const regex = /FN_TOKEN_(.*)_END_TOKEN/gm;
const code = regexGetFirstGroup(regex, new Error().stack);
return this.repo[code];
}
}
因此,我们解决方案中的想法是检查new Error().stack
的堆栈跟踪,并将我们可以提取的内容包装为令牌,然后将其放入回购中。哈奇?非常暴躁。
备注
测试表明,这实际上是非常可行的,但需要一个比我们更现代的执行环境,即ES2017+。