小tips:对setTimeout延迟的研究

Posted 恪愚

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了小tips:对setTimeout延迟的研究相关的知识,希望对你有一定的参考价值。

最近玩三国志战略版时发现它上面占领土地后一段时间内的动画效果不错,进而引发了一些探究:
原动画可以简化为一个方块上有一个类似遮罩的白色的光晕,而且不断膨胀收缩,我开始一想,这不是cssscale么?然后一定时间后将其(Node节点)remove掉。
这时候发现其每隔一段时间颜色还会变淡一些,笔者“理所当然”地用了“多次setTimeout”。

setInterval与setTimeout
每个 setTimeout 产生的任务会直接 push 到任务队列中;而 setInterval 在每次把任务 push 到任务队列前,都要进行一下判断(看上次的任务是否仍在队列中)—— 这会导致使用 setInterval 时某些间隔可能会被跳过而且可能多个定时器会连续执行。
Interval执行图示

但写完后我就立刻想到了之前研究过的“由于浏览器eventLoop和消息队列机制的原因,setTimeout通常不能准时,而且嵌套的setTimeout越多延时越大”的现象。

test_0

怎么解决呢?

既然和时间相关,必然可以用Date.now()时间戳!那么另一个方案就成型了:使用while(true)不断判断时间戳相减后是否达到目标值。但是这又产生了另一个麻烦:js是单线程的,这种方式强行将线程留在循环中会使得页面进入卡死状态,这在绝大部分场景下显然并不合适。

那怎么办呢?既然不能阻塞原有线程,那就另开一个线程去处理:js提供了webworker可以使我们利用子线程去处理一些事情。在worker线程中不能访问DOM、BOM等API。我们可以在worker中处理时间和循环,然后在主线程接收最终值,并做处理。乍一看这是一个非常好的解决方案,但是这样一来,worker线程就被while阻塞,不能再去接收其它值,而且onmessage也还属于事件循环内,如果主线程有大量阻塞依然会“不准时”。

使用webworker必须考虑到主从线程通信可能带来的耗时!这一点不能忽视。

webworker的测试必须要将页面挂到服务器上才行,不然会报错!

这时我想到了以前似乎听过一个叫“系统时间补偿”的方案,简言之就是setTimeout延迟时间不再是设置的时间了,而是“设定时间-(上一次setTimeout运行时间-真实(同步下)应该过了多长时间)”:

function timer(speed=1000,cb=function(){}) {
	clearTimeout(t);
   	let counter = 1, 
   		start = Date.now();
   	
   	function instance(){
   		console.log(1)
	    var ideal = (counter++ * speed),
	    	real = (Date.now() - start);
		//console.log(real,ideal); // 测试数据
		
	    var diff = (real - ideal);
	    // 如果有回调函数就执行
	    cb();

	    t=setTimeout(function() { instance(); }, (speed - diff)); // 通过系统时间进行修复

   	};
   	var t=setTimeout(function() { instance(); }, speed);
}

在使用“多次setTimeout”或是“setInterval”的地方替换为这个函数即可!

这里虽然还会有延迟,但是已经属于“可控范围”了(测试不管多少次调用,延迟总在1-2ms)。
test


仓促结尾

本来是想发个blink,但是越写越长,就移到这里,但是似乎又不够长。
模拟的游戏的具体代码就不放了,反正本文主要是为了探究setTimeout延时的解决方法。
不是为了宣传游戏,文章也很正经!就是某天突然有感而发罢了。
就这样吧。

以上是关于小tips:对setTimeout延迟的研究的主要内容,如果未能解决你的问题,请参考以下文章

小tips:单复选框与文字对齐问题的研究

小tips:单复选框与文字对齐问题的研究

setTimeout 函数执行代码没有任何延迟。 Javascript [重复]

SetTimeout直接执行没有延时

Mysql注入小tips --持续更新中

"setTimeout" VS "debounce" 插件 - 延迟事件的代码执行