大家在正常开发中都遇到过JavaScript中的计时器,但是有多少人对原理有深刻的理解呢?我们来分析一下定时器的实现原理。
第一,储备知识
在项目中,我们通常会遇到两种计时器,第一种是setTimeOut,第二种是setInterval。这两个计时器有以下区别:
1.setTimeout允许设置超时对象,该对象将在超时后执行,但只执行一次,没有循环
2.setInternval允许设置超时对象,该对象将在超时后执行。周期等于超时对象指定的时间,周期为无限循环
举一个简单的例子来说明:
!doctype html lang=' en ' head meta charset=' utf-8 ' title blog case/title/head body script type=' text/JavaScript ' settimeout(' alert('这是测试')',2000);setInterval(' console . log(' demo ');',1000);/script/body/html这个操作的结果是一个对话框弹出一次,然后你可以在控制台看到word demo每1秒就会输出给它
二、定时器原理
那么问题来了,当下面的代码运行时会发生什么?
!doctype html lang=' en ' head meta charset=' utf-8 ' title blog case/title/head body script type=' text/JavaScript ' settimeout(' alert(' timer!')',0);Alert ('test') /script/body/html,应该先执行Alert ('test ')还是alert ('timer ')?我们开始吧!
运行后,先弹出带有test字样的弹出框,再弹出带有timer字样的弹出框。为什么会这样?当计时器的时间为0时,是否可以执行计时器?
答案不是这样的,因为JS是众所周知的单线程,所以很多人认为在上面的例子中,它会在执行下面的语句之前阻塞等待定时器执行,但这是单线程的缺陷之一。为了解决这个问题,引入了异步机制。异步机制主要使用一个我们很少关注的知识点——浏览器多线程。什么是浏览器中的多线程?
三、浏览器多线程
在这里我们将解释一下,众所周知,JS是单线程的,但是对于浏览器来说,JS的执行只是很多现成的浏览器之一,我们称之为JS引擎线程。浏览器的其他线程在执行特定功能后,通过JS引擎线程分配给浏览器对应的线程。具体原理见图:
从这个图中,我们可以知道JS引擎线程首先执行回调函数块,然后执行click事件回调,然后执行计时器,最后执行其他线程。
让我们用下面的代码来分析它:
SetTimeout('alert ('timer!')',0);Alert ('test ')首先,JS线程读取setTimeout计时器,然后执行浏览器线程,然后跳过计时器并继续执行。这时,你可以看到弹出框是一个测试。然后,因为定时器时间为0,定时器线程一执行,就可以将弹出框为定时器的任务添加到主线程(JS引擎线程)的队列中,等待JS引擎调用。这时我们可以看到测试先弹出,然后定时器弹出。
另外要注意的是,在HTML5规范中,定时器的计时时间不能小于4ms,如果小于4ms,那么默认值就是4ms,所以在这个例子中,0,默认值是4ms,但是这个浏览器在失败的浏览器中性能是不一样的,但是这个在项目中一般印象不深,这个只是为了理解。
好吧,让我们像这样重写上面的代码,然后让我们看看效果:
脚本类型=' text/JavaScript ' console . time(' test ');setTimeout(' for(var I=0;i1000I )console.log ('timer!');',1000);console . log(' test ');console . timeend(' test ');/脚本运行的结果如下:
这里有几个知识点:
1.console.time和console.timeEnd可以获得它们之间执行的语句所花费的时间。从图中我们可以知道,测试执行时间约为1毫秒,但计时器计时时间约为1000毫秒,所以这两个语句只能计算当前引擎的执行时间,换句话说,浏览器中计时器模块的运行时间无法计算。
2.另外,我们可以看到一个现象,就是在执行定时器的时候,并不是一千个定时器的字都一次打印出来,而是增加了几百个。为什么呢?这就涉及到另一个问题。当计时器的时间到了,但是计时器中的任务没有执行,会发生什么?
如上所述,当定时器时间到了,一个任务将被添加到JS引擎线程中。无论任务中的语句是否执行,都会被添加到JS引擎线程队列中。但是剩下的未完成的陈述呢?
当程序执行定时器任务时,会加载定时器模块中已经执行的语句一次,然后继续执行定时器模块中剩余的语句。(定时器模块添加到JS引擎的任务相当于C语言的指针,指向定时器模块)
因此,setTimeout可以定义为:
在指定的时间内,将任务放入事件队列,等待js引擎空闲时执行。
第四,设置区间的使用
setInterval最基本的使用方法是直接作为循环定时器使用,这里就不做说明了
关于setInterval(fn,100)有一个误解:不是最后一次fn执行后的100ms。实际上,setInterval并不关心最后一次fn的执行结果,而是每隔100ms将fn放入主线程队列,fn和fn之间的具体间隔是不确定的,类似于setTimeout的实际延迟时间,与JS执行有关。具体的延迟效应与记忆力等因素有关。
动词(verb的缩写)计时器的可靠性
虽然计时器在大多数情况下趋于稳定,但是在计时器的使用中存在一些错误
如下图所示:
脚本类型=' text/JavaScript ' var time 1=new Date()。getTime();setInterval(函数(){ var time2=new Date()。getTime();console . log(set interval执行的时间差:“(time 2-time 1));},1000);/脚本运行的结果如下:
从图中,我们基本上可以看到计时器有一些小错误。比如第一次运行时间是1001毫秒,比我们设定的时间多了1毫秒。因此得出的结论是,计时器并不完全可靠,误差非常小。这还是在chrome浏览器上测试的结果,那么如果在IE浏览器上测试呢?
结果表明,IE浏览器下的误差更大
第六,定时器的妙用
除了定时之外,定时器还可以用来优化耗时的代码:
让我们假设有一个场景,其中500,000个节点将在某个页面中呈现。此时一般项目不宜直接渲染,因为这会占用太多内存,导致浏览器被卡住。用户误以为页面被卡住,直接关闭浏览器或扼杀进程。即使用户不关闭页面,用户体验也不好。这个时候,我们该如何解决这个问题呢?我们可以使用计时器来优化这个问题。首先,我们可以将50万个节点分成多个组,每个组中渲染的节点数量不应该太多。然后,我们可以循环使用setInterval,这样既不会阻塞JS引擎线程的运行,也不会增加渲染的消耗时间。从而实现最终的优化渲染。
七、定时器使用注意事项
如果项目中涉及到定时器,记得在定时器执行结束时,调用clearInterval或clearTimeout来清除定时器,避免定时器之间相互干扰造成一些不确定的现象
以上就是本文的全部内容。希望这篇文章的内容对你的学习或工作有所帮助。有问题可以留言交流,希望多多支持我们!