如果在网页中使用了很多计时器,那么如何管理他们就成了大问题,尤其是在处理复杂的动画效果时。 为什么这是个大问题呢?首先我们不仅需要保留很多interval计时器的引用,因为后面他们可能都要被clear(闭包是一个很好地解决方案), 同时我们还要打断浏览器的很多正常操作。尽管我们可以像上一节中那样把一个大任务切分保持浏览器的快速响应,但是垃圾回收也是 一个大问题。
启动多个计时器将大大提高垃圾回收的频率,垃圾回收时,浏览器会去扫一遍分配了内存的数据,然后去除其中没用的数据,保持内存紧凑。 计时器变量是一个特例,他们是在正常的js单线程引擎之外的线程里管理的(通常是其他的浏览器线程)。
这就埋下了一个坑。尽管多数浏览器能很好地处理这种情况,但是有些却可能表现出较长的回收周期。所以你有时可能会发现一个运行的非常流畅 的动画效果,忽然就卡顿了。减少同时使用的计时器数量将是最直接的解决方案,现在很多js动画引擎都使用一种技术:中央计时控制器。
它的好处有3点:
- 我们每次只需要使用一个计时器
- 我们能随意启停任何一个计时器
- 可以方便去除回调函数
首先让我们看一个例子:例子中我们会改变一个对象的多个属性,只使用一个timer。
Listing 8.4 A central timer control to manage multiple handlers
<html>
<head>
<title>Listing 8.4</title>
<style type="text/css">
#box { position: absolute; background-color: #00bfff; border: 2px solid #00008b; padding: 8px; }
</style>
</head>
<body>
<div id="box">Hello!</div>
<script>
var timers = { //#1
timerID: 0, //#2
timers: [], //#2
add: function(fn) { //#3
this.timers.push(fn);
},
start: function runNext() { //#4
if (this.timerID) return;
(function() {
if (timers.timers.length > 0) {
for (var i = 0; i < timers.timers.length; i++) {
if (timers.timers[i]() === false) {
timers.timers.splice(i,1);
i--;
}
}
timers.timerID = setTimeout(runNext, 0);
}
})();
},
stop: function() { //#5
clearTimeout(this.timerID);
this.timerID = 0;
}
};
var box = document.getElementById("box"), x = 0, y = 20;
timers.add(function() {
box.style.left = x + "px";
if (++x > 50) return false;
});
timers.add(function() {
box.style.top = y + "px";
y += 2;
if (y > 120) return false;
});
timers.start();
</script>
</body>
</html>
在以上代码中,我们首先创建了一个中央控制结构,其中有3个主要方法:增加回调函数,启动中央控制器,停止中央控制器。 add和stop方法都很简单。主菜在start函数中,它通过执行匿名函数包装局部变量,通过for循环执行队列中的函数,如果函数返回false, 就把他从队列里删除,比clearXXX方法方便些。最后重新执行setTimeout进入等待队列。
另外,我们注意到,回调函数是按照add的顺序执行的,而在一般浏览器中,这点无法保证。有时候这个特性可能是非常有用的。