8

我在玩 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);调用是在调用实际更改的方法之前文本。

4

3 回答 3

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.
    }
}

这是一个实现 this 的小提琴

于 2013-07-26T22:47:56.513 回答
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一次出现。

于 2013-07-26T23:09:44.123 回答
2

要理解的一件事是,它首先不是递归。如果您的函数没有适当的退出子句,这可能会永远持续下去而不会遇到爆栈。

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

如果您想要在它们之间进行 750 毫秒的递归调用,您可以执行以下操作:

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

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

于 2013-07-26T23:07:17.280 回答