在不修改递归函数的情况下增加延迟

时间:2019-02-15 20:35:22

标签: javascript ecmascript-6

让我们说说函数fib():

function fib(n) {
  if (n < 2){
    return n
  }
  return fib(n - 1) + fib (n - 2)
}

现在,假设我要在document.write中显示此递归函数的每一步,并逐步添加每次迭代的结果,每步之间的延迟为1000ms。我可以在不修改原始功能的情况下进行操作吗?也许需要另外一个功能,将该功能作为参数传递,创建输出机制,并且由于它还返回一个功能,所以递归地增加延迟?

4 个答案:

答案 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延迟。

我认为那不是你想要的。