Wenzi

二谈javascript中的定时器

蚊子前端博客
发布于 2015/04/20 06:00
以前讨论过js中的定时器,而这篇文章主要讲解定时器的最小间隔和标签页处于可见状态和不可见状态时的间隔

在以前的文章【javascript中的定时器】中,简单的介绍了一下setTimeout()和setInterval()两个定时器方法的使用和原理。不过在昨天给我的node即时聊天系统添加消息提示时,发现了定时器新的特性。当然,这对于我来说是新的发现,其实这些东西早就已经存在了。

1. 最小运行时间间隔 #

在setTimeout()和setInterval()我们能够设定时间间隔,来让下个事件大致发生在哪个时间段。假如我们设置时间间隔是0的话,那是不是就会在0ms之后执行呢,也就是立即执行。我们可以采用下面的代码输出一下:

function get(){
	var timer = null;
	var date = null;
	var diff = 0;
	var last = 0;
	var now = 0;
	var nums = 0;
	var color = '#000';
	var process = document.getElementById("process");
	timer = setInterval(function(){
		date = new Date();
		now = date.getTime();
		diff = now-last; // 前后两个时间差
		last = now;
		nums++;
		color = diff===0?'#f00':'#000';
		process.innerHTML += '<p>'+now+' (<span style="color:'+color+'">'+diff+'</span>)</p>';

		if(nums>=100){
			clearInterval(timer);
		}
	}, 0);
}
get();

我们把每次执行setInterval()前后的时间差打印到屏幕中(以下数据使用chrome 42.0.2311.90版本测试):

1429545782409 (1)    
1429545782412 (3)    
1429545782414 (2)    
1429545782420 (6)    
1429545782425 (5)    
1429545782430 (5)    
1429545782437 (7)    
1429545782443 (6)
1429545782449 (6)    
1429545782454 (5)    
1429545782460 (6)
1429545782466 (6)
1429545782471 (5)
1429545782476 (5)
...

从打印出的数据可以看出,setInterval()的时间间隔为0ms时,输出的时间差基本都在1~10ms之间,也是能在可以接受的范围内。IE11下的测试与chrome的数据基本一致,而在firefox下能够出现0的时间差。

2. 标签不可见时的定时器间隔 #

其实不管是把时间间隔设定为0ms还是其他的时间间隔,运行时都会有时间误差的,比设定的间隔多1~16ms毫秒左右,有的时候还会相差更多。

我们有时会在某个场合对标题进行闪动,提示给用户当前标签页有新消息产生:

var backup = document.title; // 存储原标题
function blink(){
    document.title = document.title == backup? "【有新消息】" : backup;            
}
blink();
timer = setInterval(blink, 500);

上面的代码能够进行500ms的标题轮流闪动,当我们处在当前标签页时,基本感觉不出定时器产生的误差。可是如果我们切换到其他的标签页或者最小化时,我们就能够看到,标题的闪动变慢了很多,差不多提升到1000ms左右了。

为了更加准确的记录时间间隔的变化,我们特此将上面的代码进行如下的补充,标题进行闪动时记录当前的毫秒时间戳,同时标记出当前标签页可见时的状态和不可见时的状态【查看演示】:

// 标题闪动
function blinkTile(title, timeout){
	var self = this;
    var timer = null;
    var backup = document.title;
    var last = 0;
    var process = document.getElementById("process");

    self.init = function(title, timeou){
        if(title != undefined){
            self.title = title;
        }
        self.timeout = timeout == undefined? 500: timeout;
    }

    self.start = function(){
        self.stop();

        function blink(){
            document.title = document.title == backup? self.title : backup;
			self.check();            
        }
        blink();
        timer = setInterval(blink, self.timeout);
    }

 	

    self.stop = function(){
        if(timer != null){
            document.title = backup;
            clearInterval(timer);
            timer = null;
        }
    }

    
    // 打印时间差,同时让滚动条在最下边
    self.check = function(){
    	var date = new Date();
		var now = date.getTime();
		var diff = now-last;
		last = now;
        process.innerHTML += '<p>'+now+' ('+diff+')</p>';
    	process.scrollTop = process.scrollHeight;
    }

    self.init(title, timeout);
}
var blink = new blinkTile('【新消息】', 500);
blink.start();

// 标签页的可见状态
var hidden, state, visibilityChange;
if (typeof document.hidden !== "undefined") {
    hidden = "hidden";
    visibilityChange = "visibilitychange";
    state = "visibilityState";
} else if (typeof document.mozHidden !== "undefined") {
    hidden = "mozHidden";
    visibilityChange = "mozvisibilitychange";
    state = "mozVisibilityState";
} else if (typeof document.msHidden !== "undefined") {
    hidden = "msHidden";
    visibilityChange = "msvisibilitychange";
    state = "msVisibilityState";
} else if (typeof document.webkitHidden !== "undefined") {
    hidden = "webkitHidden";
    visibilityChange = "webkitvisibilitychange";
    state = "webkitVisibilityState";
}

var process = document.getElementById("process");
// 添加监听器,在title里显示状态变化
document.addEventListener(visibilityChange, function() {
    if(document[state]=="hidden"){
        process.innerHTML += '<p style="color:#f00;">====== 离开  ======</p>';
    }else{
        process.innerHTML += '<p style="color:#f00;">++++++ 回来  ++++++</p>';
    }
}, false);

运行后,我们能够看到程序记录下的数据有(以下仅是部分数据):

1429547223336 (505)   
1429547223837 (501)    
====== 离开 ======    
1429547225296 (1459)    
1429547226296 (1000)  
1429547227296 (1000)   
1429547228297 (1001)  
++++++ 回来 ++++++   
1429547229137 (840)    
1429547229637 (500)

我们很清楚的看到,当标签页不可见时(“离开”后),时间差上升了1000ms左右;标签页可见时(“回来”后),时间差又恢复到了500ms左右。不过在标签页刚切换完的时候,时间差的变化比较大,后来就趋于稳定了。其实浏览器为了在标签页不可见时减少CPU的利用率和电池等的消耗,特地将时间间隔进行提高。

不过这里要指出的是,在IE11下,标签的可见状态不会影响定时器的时间间隔。

3. 如何解决时间间隔会变化的问题(2016年1月14日更新) #

在上面的章节中,介绍了setIntervalsetTimeout的时间间隔会随着标签页的可见性发生变化。但是在有的情形下,是不希望时间间隔发生变化的,那该如何解决呢。

受下面评论的启发,使用web worker可以解决这个这个问题,但是因为web worker是html5里的标准,低版本的浏览器是支持不了的。这里我们简单的介绍下web worker的使用,若想更详细的了解,还可以参考【Web Workers 的基本信息】。

当前页面:

// 创建一个worker实例
var worker = new Worker("worker.js");

// 向worker.js发送信息
worker.postMessage( 'hello world' );

var last = 0;
// 接收从worker.js发送的信息,存储在event.data中
worker.onmessage = function(event){
    var diff = event.data-last;
    last = event.data;
    $('#content').append( diff+'<br/>' );
}

// 报错信息
worker.onerror=function(error){
    console.log(error.filename,error.lineno,error.message);
}

worker.js:

// 接收前端页面发送过来的信息,存储在event.data中
onmessage = function(event){
    var data = event.data;
    setInterval(function(){
        // 向前端页面发送信息
        postMessage( Date.now() );
    }, 500)
}

在上面的例子中,可以看到,当前页面中的js和worker.js都是是通过postMessageonmessage进行互相通信的。当我们执行这些代码时,无论当前页面是否可见,setInterval每次的间隔都是**500**。

因此,我们可以在worker.onmessage中进行相应的DOM操作。

Simple Empty
No data