定时器

timer-thread

由于JavaScript单线程的特点,异步的事件(click、timer、xhr…)作为消息都被放入一个消息队列中,每个消息与一个方法相关(即回调函数)。消息队列一直处于等待通知的状态中(异步轮询),当JavaScript主线程空闲下来,即栈(stack)中为空时,主线程会向消息队列发出通知,然后消息队列会依次一个一个将消息队列中的事件按先进先出的原则(不同浏览器具体实现方式可能不同)取出来,与消息相关的方法被依次放入栈中执行。
消息在处理的过程中(即回调函数执行过程)是”Run-to-completion”的,即一股劲儿执行完毕不会被打断的,多线程的语言比如java可能会停止当前线程去开启其他线程,而JavaScript不会,所以导致如果你的消息处理时间过长,浏览器会阻塞,并提示script执行过久,所以最好把处理过长的消息拆开处理。
具体参考MDN and S.O.

timer and thread

  1. 定时器任务的执行时刻并不是定时器设定的延迟时间后的时刻,由于JavaScript 单线程的特点,消息队列中的任务执行时刻是由何时放入以及队列当前前面的任务执行所花费的时间一同决定的。队列中前面的任务花费时间越长,该任务等待的时间越久。

diffrences between timeout and interval

1
2
3
4
5
6
7
8
9
10
式1:
setTimeout(function repeatMe() {
/* Some long block of code... */
setTimeout(repeatMe, 10);
}, 10);
式2:
setInterval(function() {
/* Some long block of code... */
}, 10);
  1. 式1能保证方法的两次执行间隔至少为10,而式2无法保证,即setTimeout定时器能保证至少要等待的时间。式2中,假设该定时器的前一个任务实例执行过程中(此时队列里已经没有该任务实例了),setInterval又放入队里一个任务实例,且中间没有其他任务,那么这两个任务间隔就为近乎0了。

  2. setTimeout定时器会将一个消息任务在指定的时间后放入消息队列中;而setInterval定时器尝试在每隔指定的时间后向消息队列放入任务,但是为了保证消息队列中最多只有一个该任务(方法的实例),如果setInterval在指定放入任务的时刻发现队列中已经存在了该任务,则会跳过该任务的放入。

    dealing with expensive processing

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    原code:
    var start = new Date().getTime()
    var tbody = document.getElementsByTagName("tbody")[0];
    for (var i = 0; i < 20000; i++) {
    var tr = document.createElement("tr");
    for (var t = 0; t < 6; t++) {
    var td = document.createElement("td");
    td.appendChild(document.createTextNode(i + "," + t));
    tr.appendChild(td);
    }
    tbody.appendChild(tr);
    }
    var end = new Date().getTime();
    用定时器拆开执行:
    var start = new Date().getTime(),end;
    var rowCount = 20000;
    var divideInto = 4;
    var chunkSize = rowCount/divideInto;
    var iteration = 0;
    var table = document.getElementsByTagName("tbody")[0];
    setTimeout(function generateRows(){
    var base = (chunkSize) * iteration;
    for (var i = 0; i < chunkSize; i++) {
    var tr = document.createElement("tr");
    for (var t = 0; t < 6; t++) {
    var td = document.createElement("td");
    td.appendChild(
    document.createTextNode((i + base) + "," + t +
    "," + iteration));
    tr.appendChild(td);
    }
    table.appendChild(tr);
    }
    iteration++;
    if (iteration < divideInto){
    setTimeout(generateRows,0);
    }else {
    end = new Date().getTime();
    console.log(end-start);
    }
    },0);

效果是显而易见的,原code体验是很糟糕的。Firefox下直接卡死,弹出”无响应”,运气好过一会儿会出现结果;IE暂时卡死,过了好久才反应过来;Google很强大,但是选项卡上的小圆圈一直转也是蛮烦人。

用定时器把任务拆成4个小块儿的异步执行以后,执行时间上必然是增多了,并且是多次渲染页面。但是体验上至少缓和了许多,且不影响后面的同步代码的执行。

central timer control

定时器的滥用,如在动画中使用过多的定时器等操作会引发GC(垃圾回收器)的频繁工作,而GC工作)是需要占用相当大比例的运行时间,所以会严重影响性能,所以我们需要一个定时器控制器,来实现页面只有一个定时器处理异步的任务,且可以暂停或启动,使操作变得灵活可靠。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var timers = {
timerID: 0,
timers: [],
add: function(fn) {
this.timers.push(fn);
},
start: function() {
if (this.timerID) return;
(function runNext() {
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() {
clearTimeout(this.timerID);
this.timerID = 0;
}
};
以上的定时器可以添加多个任务,启动该定时器可以自动的分割任务,当然需要任务中通过返回不返回false进行判断,并将其放入消息队里里异步执行。
任务何以通过返回false来从定时器的任务数组timers中把自己删除,当任务数组中没有任务是,定时器不在进行。且在必要时可以停止定时器的运行。
以上用一个定时器实现了异步执行多个任务,避免过多的定时器引发GC频繁工作导致影响浏览器性能