返回深入学习ExtJS 2.2开发系列连载教程目录
定时任务是指不需要通过手动调用,其执行函数或代码会在指定的时间自动运行。在JavaScript中提供了setTimeout和setInterval。clearInterval和clearTimeout两对函数进行定时任务的处理。其中setTimeout和setInterval用来实现在指定的时间间隔之后执行指定的函数,它们都有两个参数,一个是将要执行的代码字符串,还有一个是以毫秒为单位的时间间隔,不同的setTimeout只执行一次,而setInterval按时间间隔反复执行。
clearInterval和clearTimeout函数则是分别用来取消setTimeout和setInterval的设定的定时任务。如果通过setTimeout的定时任务还没有启动执行,那么只要传入该定时任务的引用到clearTimeout中就可以取消该任务。clearInterval也一样,用来取消setInterval设定的定时任务,它们都不能中断正在执行中的函数。
3.9.1 有状态定时任务管理
既然我们能通过来setInterval实现连续间隔地执行指定任务(即函数)。但是任务执行时有先后的关联,即状态性。比如需要根据上一次的执行看看其结果是否满足,如果满足就不去继续执行,而中断该任务。
而setInterval做不到,它到了一定的间隔时间就会执行,它不会去关注上一次执行是否结束,它是另外开启新的线程并发执行。那么如何去实现任务的状态管理呢。有二种方法,一是采用setTimeout模拟,二是采用setInterval实现。
1、setTimeout模拟 一般来说,所有语言的函数都支持递归调用,只要让函数内部通过setTimeout隔一段时间执行该函数,而在setTimeout中任务(即当前函数)中也会继续通过其函数内部的setTimeout执行下一次。这样就实现任务的连续有序地执行,其状态可以就可以得到并进行相关的处理。如下面代码:
changeSize : function(o) {
var el = o.el, cache =
{},startTime = new Date().getTime();
if (!el ||!el.tagName)
{ return; } var sw = o.sw= o.sw, sh = o.sh, ew = o.ew, eh= o.eh;
var wDelta, hDelta, interval, d, t, nw, nh;
var d = o.d ,t= o.t ,interval = d / t , nw = sw, nh = sh;
var wDelta = (ew - sw) / t , hDelta = (eh - sh) / t;
var step = function()
{// 每步怎么做 ①
if (nw < ew) {el.style.width = nw + wDelta + ''px''; nw = nw + wDelta; }
if (nh < eh) {el.style.height = nh + hDelta + ''px''; nh = nh + hDelta; }
tout = setTimeout(step, interval); ②
if ((nh >= eh && nw >= ew)||(wDelta == 0 && hDelta == 0) { ③
clearTimeout(tout);};
step();} ④
该代码是用来实现元素大小变化的动画,这里去掉了相关的处理,只留了基本的功能(具体见9.2节),我们先不去管①处之前的代码。①处定义一个step函数,在该函数中①②之间是具体的业务处理, ②处是通过setTimeout来实现连续有状态的任务管理。它会每隔指定的interval时间间隔去执行step函数。这个执行得有结束的时候,这个处理就是在③处实现,它判断变化量nh和nw是否大于或等于eh,ew结束量,等于说明就不用再执行该step函数,采用clearTimeout取消定时任务。这里的nh和nw是所说的定位任务的状态,每次执行中其值都会变化。
我们可以得出结论采用setTimeout实现有状态定位任务只要把setTimeout写在任务函数之中就可以,不过这样和业务耦合高,同时这样的实现会有一定上时间漂移。
2、setInterval实现 setTimeout实现业务耦合高, 我们也可以采用setInterval实现,每次运行定时任务之前判断上一次任务是否执行完成,没有执行完成就等待。Prototype中的PeriodicalExecuter就是采用这样的方式,我们把它改造一下:
var PeriodicalExecuter = function(cb, interval) {
var nowExec = false;
var te = function()
{
if (!nowExec) {try {nowExec = true; cb();}
finally {nowExec = false; }}
};
setInterval(te, interval);}
在上面代码中通过一个标识nowExec来标识上一次任务是否执行完成,完成就是false。表示当前不是正在运行中。这里的setInterval执行的定时任务不是传入的任务,而是把传入的任务进行了包裹,加上判断其是否正在执行,如果正在就不执行。但是该包裹的函数都是每次都通过setInterval进行了调用。prototype的定位任务有点简单。
3、Ext.util.DelayedTask Ext.util.DelayedTask类是一个非常有意思的状态任务应用,从名字上就可以看出它是推迟任务管理。它用来实现隔指定时间长度之后执行任务,与PeriodicalExecuter不同,它不是针对于同个任务,而可能是多个任务,上一次任务如果还没有执行,它不是等待,而是取消转而执行当前任务。
比较典型的应用是ExtJS的事件配置中的buffer配置项,它用来指定延迟一段时间执行事件监听函数,如果在这个延迟时间之内,又有另外触发要执行其监听函数,上一次没有执行的任务取消,而又延迟buffer提定的时间去执行新任务。
在Ext.util.Event中有两个这样的函数用来处理buffer和delay配置参数:
var createBuffered = function(h, o, scope){
var task = new Ext.util.DelayedTask();
return function()
{
task.delay(o.buffer,h,scope,
Array.prototype.slice.call(arguments, 0));};
};
var createDelayed = function(h, o, scope)
{
return function(){
var args = Array.prototype.slice.call(arguments, 0);
setTimeout(function(){h.apply(scope, args);}, o.delay || 10);};
};它们肯定是有区别的,createDelayed是用来进行延迟指定时间后去执行任务。它采用setTimeout完成,这个对于每个任务都是一定会执行的。而createBuffered不一同,对于同一个事件监听,它定义了一个任务DelayedTask,如果在其buffer时间里多次触发,那么它只执行最后一次触发的任务。
我们来看一下DelayedTask的delay函数:
this.delay = function(delay, newFn, newScope, newArgs){
if(id && delay != d)
{ this.cancel(); } ①
d = delay;
t = new Date().getTime();
fn = newFn || fn;
scope = newScope || scope;
args = newArgs || args; ②
if(!id)
{ id = setInterval(call, d);} }; ③
当构建一个DelayedTask实例,我们就可以多次来调用其delay方法来延迟其任务的执行。上面的函数有四个参数,其中命名有new前缀的都是可以省略采用实例中的任务(即通过构建函数参数传入。在①处,它用来判断该实例是否已经有任务正在执行,如果正在执行的任务的时间间隔和当前参数中指定的时间间隔不一样,就通过cancel取消正在执行中任务,取消就是通过clearInterval来取消,并把id设为null。
在①②之间的代码就是把参数中值扩大到整个实例范围让其实例所有函数都能访问。③处是每隔指定的时间来执行当前实例的call函数。这个call函数当然是包裹函数,用来包裹定位任务,即参数中的回调函数。这也就是为什么要把参数中传入值扩大范围。
var call = function(){
var now = new Date().getTime();
if(now - t >= d)
{
clearInterval(id);
id = null;
fn.apply(scope, args || ); } };
在call函数中,它在执行任务之前就会取消下一次定时任务,并把id设为null。可以看出该定位尽管是通过setInterval,它至多只运行一次。这里可以看作是采用setInterval模拟setTimeout。
①②③④⑤⑥⑦⑧⑨⑩
3.9.2定时任务管理器
上面的定时任务都是单个任务,如果需要对一组定时任务进行操作怎么办?ExtJS提供了Ext.TaskMgr实例用来进行默认组的定时任务管理。它是通过Ext.util.TaskRunner类完成。
Ext.util.TaskRunner = function(interval){
interval = interval || 10;
var tasks = , removeQueue = ;
var id = 0, running = false;
var stopThread = function()
{.. .. ..};
var startThread = function()
{.. .. ..};
var removeTask = function(t)
{.. .. .. };
var runTasks = function()
{ .. .. .. };
this.start = function(task)
{.. .. .. };
this.stop = function(task)
{.. .. .. };
this.stopAll = function()
{.. .. ..};
TaskRunner主要的作用是对一组定时任务进行统一地运行管理,我们可以通过其start、stop函数来运行或停止指定的任务。另外还可以通过stopAll来停止所有正在运行的任务。其实TaskRunner和第九章中的动画实例管理器很相似。
如果我们需要启动一个定时任务,我们要通过其start函数,它有task参数是一个配置对象,用来指定执行的相关配置,其中run是要执行的任务函数,interval是指定它隔多长时间执行一次,但是不能小于整个管理器设定的interval,其默认是10ms。duration是用来指定任务的执行总的时间,如果没有指定时间,也可以通过指定repeat来指定次数。scope是run函数的作用域,args是run函数的参数。我们看一下start:
this.start = function(task){
tasks.push(task);
task.taskStartTime = new Date().getTime();
task.taskRunTime = 0;
task.taskRunCount = 0;
startThread();
return task; };
这里只要把task存到任务集合中去,再初始化一些值,之后通过startThread来启动一个新的线程执行任务。而它是通过setInterval(runTasks, interval);来执行runTasks函数,如果经运行了,就不会再次启动setInterval。
var runTasks = function(){
if(removeQueue.length > 0)
{ ①
for(var i = 0, len = removeQueue.length; i < len; i++){
tasks.remove(removeQueue[i]); }
removeQueue = ;
if(tasks.length < 1){ stopThread();return; }}
var now = new Date().getTime();
for(var i = 0, len = tasks.length; i < len; ++i){ ②
var t = tasks[i];
var itime = now - t.taskRunTime;
if(t.interval <= itime){ ③
var rt = t.run.apply(t.scope || t, t.args || [++t.taskRunCount]);
t.taskRunTime = now;
if(rt===false||t.taskRunCount===t.repeat){removeTask(t);return;}
}
if(t.duration&&t.duration<=now-t.taskStartTime)){removeTask(t);}④
}};该函数分成两部分,①处是处理删除任务队列中的任务,②是处理运行任务队列中的任务。通过①处可以看出removeTask并不是从tasks中除去,而把其引用加到removeQueue中,这样能避免影响正在运行中的任务。它的删除是在下一次运行中。
②处是对每个任务都进行执行,这个执行是根据其配置项的参数来进行的,③是根据时间或其重复次数来判断当前任务是否执行。如果超过指定的时间就运行④处代码,把它移到removeTask中去。
该类在Form中有应用。