Javascript如何管理递归调用?

时间:2013-07-26 22:45:42

标签: javascript

我一直在玩JavaScript,并注意到一种奇怪的行为(至少对我来说很奇怪......)

所以我在这里做了SSCCE

我有一个名为“myDiv”的div

function changeText(text){
    document.getElementById("myDiv").innerHTML=text;
}

function recursiveCall(counter){
    if(counter){
        setTimeout(function(){
            recursiveCall(--counter);
            changeText(counter);
        },750);
    }
}

recursiveCall(10);

实例:http://jsfiddle.net/T645X/

所以我正在更改div上的文本,结果是文本从9变为0,而我认为从0到9,因为递归changeText(counter);调用在调用实际更改文本的方法之前。

3 个答案:

答案 0 :(得分:8)

该函数包含异步的超时。

setTimeout(function(){
    recursiveCall(--counter);// calls the next function, which will call the next 
                             // and print in a timeout
    changeText(counter);  // print
},750);

在递归调用达到超时之前更改文本。

如果您愿意,可以从超时外部移动打印调用,这将导致预期的行为:

function recursiveCall(counter){
    if(counter){
        recursiveCall(--counter);            
        setTimeout(function(){
            changeText(counter);
        },750);
    }
}

(虽然,请注意,这里的打印时间不同,我们依赖于未定义的行为,假设它首先打印只是因为我们把计时器放在第一位)

如果您希望它仍然延迟打印,您可以告诉它已完成的功能。递归仍将在最初完成,但每个级别将告诉它上面的级别它已完成:

function recursiveCall(counter,done){
    if(counter){
        // note how recursion is done before the timeouts
        recursiveCall(counter-1,function(){ //note the function
            setTimeout(function(){          //When I'm done, change the text and let the 
                changeText(counter-1);      //next one know it's its turn.
                done(); // notify the next in line.
            },750);
        });
    }else{
        done(); //If I'm the end condition, start working.
    }
}

<强> Here is a fiddle implementing this

答案 1 :(得分:4)

严格来说这里没有递归

setTimeout的调用只会将回调添加到计划的计时器事件列表中。

很多时候,您的浏览器只是坐在那里等待事件,它会处理这些事件(即运行您的事件处理程序),然后返回等待事件。

所以在这种情况下你正在做的是:

   recursiveCall(10)
   timer event and callback added to the queue
   function exits

... waits 750 ms ...

   timer event fires, callback pulled from the queue and invoked
      -> recursiveCall(9) invoked
        ->  timer event and callback added to the queue
      -> changeText(9) invoked
   callback function exits

... waits 750 ms ...

   timer event fires, callback pulled from the queue and invoked
      -> recursiveCall(8) invoked
        ->  timer event and callback added to the queue
      -> changeText(8) invoked
   callback function exits

and so on...

我称之为伪递归,因为虽然它看起来有点像经典递归,但每次调用都是从相同的“堆栈帧”开始的,即如果你要求堆栈跟踪,那么通常只会一次(或在你的情况下有时是两个)recursiveCall一次出现的实例。

答案 2 :(得分:2)

要理解的一点是,它首先不是递归;如果你的函数没有正确的退出子句,这可以永远持续下去,而不会遇到一个爆炸的堆栈。

原因是您传递给setTimeout()的任何函数都在当前执行上下文的外部运行;换句话说,代码“突破”了你的功能。

如果你想要一个750毫秒的递归调用,你可以这样做:

function recursiveCall(counter, fn)
{
    if (counter) {
        recursiveCall(--counter, function() {
            changeText(counter);
            setTimeout(fn, 750);
        });
    } else if (fn) {
        fn(); // start chain backwards
    }
}

它在recurses时创建一个回调链,并且exit子句设置整个链运动,向后:)