这是在学习(nodejs基础2中第1-2节)自己犯的一个错误,记下来防止后面再次发生吧。先把错误代码放上:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>moveball</title>
<style>
.ball
{
width: 100px;
height: 100px;
border-radius: 50px;
margin-top: 50px;
}
.ball1
{
background-color: pink;
}
.ball2
{
background-color: orange;
}
.ball3
{
background-color: seagreen;
}
</style>
</head>
<body>
<div class="ball ball1" style="margin-left: 0;"></div>
<div class="ball ball2" style="margin-left: 0;"></div>
<div class="ball ball3" style="margin-left: 0;"></div>
<script>
var ball1 = document.querySelector('.ball1');
var ball2 = document.querySelector('.ball2');
var ball3 = document.querySelector('.ball3');
function animate(ball,target,callback)
{
setTimeout(function(){
var marginLeft = parseInt( ball.style.marginLeft,10 );
if( marginLeft === target )
{
callback && callback();
}else if( marginLeft < target ){
marginLeft++;
}else{
marginLeft--;
}
}
ball.style.marginLeft = marginLeft + 'px';
animate(ball,target,callback);
},15);
}
animate(ball1,100,function(){
animate(ball2,200,function(){
animate(ball3,300,function(){ //运行到这里就会停止
animate(ball3,150,function(){
animate(ball2,150,function(){
animate(ball1,150,function(){});
});
});
});
});
});
</script>
</body>
</html>
运行上面这个代码后,会发现小球就不会回到150这个点的,但奇怪的是如果按照1-2-3-1-2-3这样的运动顺序:
animate(ball1,100,function(){
animate(ball2,200,function(){
animate(ball3,300,function(){
animate(ball1,150,function(){
animate(ball2,150,function(){
animate(ball3,150,function(){});
});
});
});
});
});
小球就都会移动到150这个点,但能明显感觉到这个过程并不是匀速,而是越来越快,那么第一感想到的就是需要清理定时器(因为这情况简直跟setInterval()动画出现的问题一样):
function animate(ball,target,callback)
{
this.time = setTimeout(function(){
var marginLeft = parseInt( ball.style.marginLeft,10 );
if( marginLeft === target )
{
clearTimeout( this.time );
callback && callback();
}else if( marginLeft < target ){
marginLeft++;
}else{
marginLeft--;
}
}
ball.style.marginLeft = marginLeft + 'px';
animate(ball,target,callback);
},15);
}
运行上面这个代码后,会发现小球终于匀速地滚到了目标位置,但这并没有解决一个问题,那就是小球如果一旦按照1-2-3-3-2-1的顺序执行,依然会卡在1-2-3这个地方后面的3-2-1就根本不执行。很是折腾,打开调式面板会发现运行正常,后台有‘test’打印出来,但就是不执行callback():
if( marginLeft === target )
{
console.log('test');
callback && callback();
}
在细看html标签中magin-left值会保持300的跳动,才明白原来这里的setTimeout并没有停止,所以就没办法进入自身的下一次定时器。
那么为什么没有停止呢?回来再次阅读if()代码块找到了一个逻辑错误之处:
this.time = setTimeout(function(){
var marginLeft = parseInt( ball.style.marginLeft,10 );
if( marginLeft === target )
{
clearTimeout( this.time );
callback && callback();
}else if( marginLeft < target ){
marginLeft++;
}else{
marginLeft--;
}
}
//这里写在判断语句之外就会出一个定时器泄漏的情况
//无论marginLeft 与 target是大于等于小于都会再次执行animate()
//即找不到一个退出当前定时器的条件,相当于设置了一个setInterval()
ball.style.marginLeft = marginLeft + 'px';
animate(ball,target,callback);
},15);
其实细想一旦setTimeout()动画需要你去清理掉定时器,那么肯定没有给出退出条件。setTimeout()理论上是仅执行一次无需清理,所以最后代码修改了一下,那就是在marginLeft != target时才再次执行animate();
注意:理解这个错误后就会明白为什么1-2-3-3-2-1不能执行了,而加入清理定时器后1-2-3-1-2-3又能执行(其实这也没有正确执行,最后一次的setTimeout依然没有被清除)
正确的代码是这样:
function animate(ball,target,callback)
{
setTimeout(function(){
var marginLeft = parseInt( ball.style.marginLeft,10 );
if( marginLeft === target )
{
callback && callback();
}else{
if( marginLeft < target ){
marginLeft++;
}else{
marginLeft--;
}
ball.style.marginLeft = marginLeft + 'px';
animate(ball,target,callback);
}
},15);
}
正确执行后也就不需要什么清理定时器了。
可见在使用setTimeout()制作动画时一定要注意逻辑关系,一定要给出退出条件,不然就会出错。