JavaScript定时器与执行机制介绍

从js执行机制说起
浏览器(或者说js引擎)执行js的机制是基于事件循环。
由于js是单线程,所以同一时间只能执行一个任务,其他任务就得排队,后续任务必须等到前一个任务结束才能开始执行。
为了避免因为某些长时间任务造成的无意义等待,js引入了异步的概念,用另一个线程来管理异步任务。
同步任务直接在主线程队列中顺序执行,而异步任务会进入另一个任务队列,不会阻塞主线程。等到主线程队列空了(执行完了)的时候,就会去异步队列查询是否有可执行的异步任务了(异步任务通常进入异步队列之后还要等一些条件才能执行,如ajax请求、文件读写),如果某个异步任务可以执行了便加入主线程队列,以此循环。
js定时器
js的定时器目前有三个:settimeout、setinterval和setimmediate。
定时器也是一种异步任务,通常浏览器都有一个独立的定时器模块,定时器的延迟时间就由定时器模块来管理,当某个定时器到了可执行状态,就会被加入主线程队列。
js定时器非常实用,做动画的肯定都用到过,也是最常用的异步模型之一。
有时候一些奇奇怪怪的问题,加一个settimeout(fn, 0)(以下简写settimeout(0))就解决了。不过,如果对定时器本身不熟悉,也会产生一些奇奇怪怪的问题。
settimeout
settimeout(fn, x)表示延迟x毫秒之后执行fn。
使用的时候千万不要太相信预期,延迟的时间严格来说总是大于x毫秒的,至于大多少就要看当时js的执行情况了。
另外,多个定时器如不及时清除(cleartimeout),会存在干扰,使延迟时间更加捉摸不透。所以,不管定时器有没有执行完,及时清除已经不需要的定时器是个好习惯。
html5规范规定最小延迟时间不能小于4ms,即x如果小于4,会被当做4来处理。 不过不同浏览器的实现不一样,比如,chrome可以设置1ms,ie11/edge是4ms。
settimeout注册的函数fn会交给浏览器的定时器模块来管理,延迟时间到了就将fn加入主进程执行队列,如果队列前面还有没有执行完的代码,则又需要花一点时间等待才能执行到fn,所以实际的延迟时间会比设置的长。如在fn之前正好有一个超级大循环,那延迟时间就不是一丁点了。
(function testsettimeout() {
const label = 'settimeout';console.time(label);settimeout(() => { console.timeend(label);}, 10);for(let i = 0; i { i += 1; i === 5 && clearinterval(timer); console.log(`第${i}次开始`, date.now() - start); for(let i = 0; i { console.timeend(label);});})();
edge输出:setimmediate: 0.555 毫秒
很明显,setimmediate设计来是为保证让代码在下一次事件循环执行,以前settimeout(0)这种不可靠的方式可以丢掉了。
其他常用异步模型
requestanimationframe
requestanimationframe并不是定时器,但和settimeout很相似,在没有requestanimationframe的浏览器一般都是用settimeout模拟。
requestanimationframe跟屏幕刷新同步,大多数屏幕的刷新频率都是60hz,对应的requestanimationframe大概每隔16.7ms触发一次,如果屏幕刷新频率更高,requestanimationframe也会更快触发。基于这点,在支持requestanimationframe的浏览器还使用settimeout做动画显然是不明智的。
在不支持requestanimationframe的浏览器,如果使用settimeout/setinterval来做动画,最佳延迟时间也是16.7ms。 如果太小,很可能连续两次或者多次修改dom才一次屏幕刷新,这样就会丢帧,动画就会卡;如果太大,显而易见也会有卡顿的感觉。
有趣的是,第一次触发requestanimationframe的时机在不同浏览器也存在差异,edge中,大概16.7ms之后触发,而chrome则立即触发,跟setimmediate差不多。按理说edge的实现似乎更符合常理。
edge输出:requestanimationframe: 16.66 毫秒
chrome输出:requestanimationframe: 0.698ms
但相邻两次requestanimationframe的时间间隔大概都是16.7ms,这一点是一致的。当然也不是绝对的,如果页面本身性能就比较低,相隔的时间可能会变大,这就意味着页面达不到60fps。
promise
promise是很常用的一种异步模型,如果我们想让代码在下一个事件循环执行,可以选择使用settimeout(0)、setimmediate、requestanimationframe(chrome)和promise。
而且promise的延迟比setimmediate更低,意味着promise比setimmediate先执行。
function testsetimmediate() {
const label = 'setimmediate';console.time(label);setimmediate(() => { console.timeend(label);});}
function testpromise() {
const label = 'promise';console.time(label);new promise((resolve, reject) => { resolve();}).then(() => { console.timeend(label);});}
testsetimmediate();
testpromise();
edge输出:promise: 0.33 毫秒 setimmediate: 1.66 毫秒
尽管setimmediate的回调函数比promise先注册,但还是promise先执行。
可以肯定的是,在各js环境中,promise都是最先执行的,settimeout(0)、setimmediate和requestanimationframe顺序不确定。
process.nexttick
process.nexttick是nodejs的api,比promise更早执行。
事实上,process.nexttick是不会进入异步队列的,而是直接在主线程队列尾强插一个任务,虽然不会阻塞主线程,但是会阻塞异步任务的执行,如果有嵌套的process.nexttick,那异步任务就永远没机会被执行到了。
使用的时候要格外小心,除非你的代码明确要在本次事件循环结束之前执行,否则使用setimmediate或者promise更保险。

自动驾驶系统时间同步的前世今生
第9集:跨平台应用程序的性能
从滴滴被罚看看自动驾驶信息安全
干货 | 单片机、ARM、FPGA、嵌入式的区别和特点有哪些呢?
电子设计自动化技术的应用
JavaScript定时器与执行机制介绍
终于高傲的苹果低头 即便如此 也并不能帮它夺回中国市场
特斯拉model3国内价格要涨?搭载众多黑科技
同频正弦信号间相位差测量的设计?
场景点亮 标准驱动 | 深开鸿、中软国际智联南京都市圈
离心风机选用高压软起动器需要考虑哪些问题?
人工智能带你来一场穿越千年的智能矿山“梦游记”
CERNEX为无线和光通信的不断增长而提供产品和服务
细数光伏逆变器的八大智能功能
热导式流量开关原理_热导式流量开关的优缺点
OPPO官宣张子枫参与视频同学录大赛,看点多多惊喜满满
开发AGV时需要避免的5个常见误区 你知道哪些?
莱姆电子的HTT系列电流传感器概述
rk3566和s922参赛对比
平头哥半导体:玄铁910芯片支持运行Android 10操作系统