让我们说说函数fib():
function fib(n) {
if (n < 2){
return n
}
return fib(n - 1) + fib (n - 2)
}
现在,假设我要在document.write中显示此递归函数的每一步,并逐步添加每次迭代的结果,每步之间的延迟为1000ms。我可以在不修改原始功能的情况下进行操作吗?也许需要另外一个功能,将该功能作为参数传递,创建输出机制,并且由于它还返回一个功能,所以递归地增加延迟?
答案 0 :(得分:4)
否,但是将其编写为 generator 会为您提供一个有用的接口,以实现类似的功能
function*fib() {
for (let a = 1, b = 1, c = 0;; c = a+b, a = b, b = c) yield a;
}
const sleep = ms => new Promise(resolve => setTimeout(() => resolve(), ms));
const gen = fib();
// then, use it step by step
console.log(gen.next().value);
console.log(gen.next().value);
// OR with a delay inbetween
async function slowly() {
for (let v of gen) {
console.log(v);
await sleep(1000);
}
}
slowly();
答案 1 :(得分:1)
由于原始函数是同步的,如果不进行修改,就不能像异步一样真正调用它。
JavaScript允许您覆盖符号fib
之类的符号。这使您可以重新定义它,只要您想要。我不知道,也许可以通过动态添加的行为使它异步,但这太复杂了。
但是,您说“我想显示此递归函数的每个步骤...在步骤之间延迟1000毫秒”。您可以轻松完成此操作,因为您可以同步调用fib
,但可以异步打印结果!示例:
function fib(n) {
if (n < 2){
return n
}
return fib(n - 1) + fib (n - 2)
}
var queue = [];
var depth = 0;
var manageCall = function(fn){
return function() {
++depth;
let result = fn.apply(this, arguments);
--depth;
queue.push(" ".repeat(depth)+fn.name+"("+arguments[0]+") = "+result);
return result;
};
};
var fib = manageCall(fib);
fib(8);
var printDelayed = function() {
if (queue.length != 0) {
console.info(queue.pop());
setTimeout(printDelayed, 1000);
}
}
printDelayed();
fib
不变,但是可以遵循递归的执行方式。
答案 2 :(得分:1)
是的,所以...您可能实际上可以做到,但是您将必须变得很有创造力。这是非常糟糕的代码,可能需要进行一些调整才能真正起作用,但是可以想象,您可以再进一步调整一下以获得所需的内容。
我们在做什么
因此,我们将删除传递给我们的mangler函数waitAndPrintFunc
的已定义函数的胆量。该函数将把该函数输出为字符串,然后使用它来重建通过eval
执行的Frankenstein函数。
请注意:请勿在生产环境中使用 EVER 。这段代码真是令人讨厌,只是为了证明可以做到这一点。
//global
let indexCounter = 0;
const waitAndPrintFunc = (func) => {
let wholeFunc = func.toString();
const funcName = wholeFunc.slice(8, wholeFunc.indexOf('(')).replace(' ', '');
let funcBody = wholeFunc.slice(wholeFunc.indexOf('{') + 1, wholeFunc.lastIndexOf('}'));
const returnIndex = funcBody.indexOf(`return ${funcName}`);
const meatyPart = funcBody.slice(returnIndex + 7);
wholeFunc = wholeFunc.split('');
funcBody = funcBody.split('');
funcBody.splice(
returnIndex,
funcBody.length - returnIndex,
`document.querySelector('.output').appendChild("step \${indexCounter++}: \${eval(meatyPart)}"); setTimeout(() => {${meatyPart}}, 1000);`
);
wholeFunc.splice(0, 9 + funcName.length, 'const MyRiggedFunction = ');
wholeFunc.splice(wholeFunc.indexOf(')') + 1, 0, ' => ');
wholeFunc.splice(wholeFunc.indexOf('{') + 1, wholeFunc.lastIndexOf('}'), ...funcBody);
console.log(wholeFunc.join(''))
eval(`${wholeFunc.join('')} ; MyRiggedFunction(1)`);
};
function fib(n) {
if (n < 2) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
waitAndPrintFunc(fib);
答案 3 :(得分:0)
我看到我可以拦截一个函数调用,如果我拦截fib(n),它应该被它自己的递归调用拦截,对吗? https://bytes.babbel.com/en/articles/2014-09-09-javascript-function-call-interception.html我会尝试的
不,你不能那样做。
您绝对可以“点击” fib
以便添加一些console.log
:
// original function
function fib(n) {
if (n < 2){
return n
}
return fib(n - 1) + fib (n - 2)
}
// rebind the var `fib` to a wrapper to the original `fib`
var fib = ((orig) => (n) => {
console.log('tap:', n);
return orig(n)
}
)(fib);
console.log("result:", fib(7));
但是,如果前提是您不能修改原始的fib
,则意味着它仍然可以作为同步功能:添加延迟意味着{{1 }}变为异步。
但是fib
本身的返回值是递归的,因此它使用加法运算符(fib
),因此期望立即数而不是延迟的值。
如果约束是您不能修改原始的fib(n - 1) + fib(n - 2)
,而只能对其进行修改,则无法根据给定的代码添加超时。
说,您可以肯定地使用该函数,并每1000毫秒安排一次fib
:但是,这意味着该函数已经完成了要执行的工作,对于每个步骤来说,只需console.log
延迟。
我认为那不是你想要的。