我一直在玩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);
所以我正在更改div上的文本,结果是文本从9变为0,而我认为从0到9,因为递归changeText(counter);
调用在调用实际更改文本的方法之前。
答案 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.
}
}
答案 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子句设置整个链运动,向后:)