为元数据分配和查询Javascript箭头函数



问题相当简单。我们需要给函数注入一个参数,然后简单地从函数体中提取该参数。我将用打字稿呈现大纲。。。

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,并且可以有awaits,因此不能依赖于通过多个交错调用来保持值
  • 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+。

最新更新