JavaScript定时器实现的原理分析


Posted in Javascript onDecember 06, 2016

JavaScript中的定时器大家基本在平时的开发中都遇见过吧,但是又有多少人去深入的理解其中的原理呢?下面我们就来分析一下定时器的实现原理。

一、储备知识

在我们在项目中一般会遇见过这样的两种定时器,第一种是setTimeOut,第二种是setInterval,这两种定时器有如下的区别:

1、setTimeout允许设置一个超时对象,超时后执行这个对象,但是只执行一次,无周期

2、setInternval允许设置一个超时对象,超时后执行这个对象,周期等于超时对象指定的时间,周期为无限循环

举一个简单的例子来说明一下:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>blog案例</title>
</head>
<body>
 <script type="text/javascript">
  setTimeout("alert('this is test')",2000);
  setInterval("console.log('demo');",1000);
 </script>
</body>
</html>

这个运行后的结果是弹出了一次对话框,然后在控制台可以看到每1秒钟会向其中输出demo字样

二、定时器原理初识

那么问题来了,如下的代码运行的时候会出现什么情况呢?

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>blog案例</title>
</head>
<body>
 <script type="text/javascript">
  setTimeout("alert('定时器!')",0);
  alert("测试")
 </script>
</body>
</html>

是先执行alert("测试"),还是先执行alert("定时器")呢,那么我们就来运行一下吧!

运行后的结果是先弹出测试字样的弹出框,然后才弹出定时器字样的弹出框,为什么会这样呢?不是定时器的时间为0就即可执行吗?

答案不是这样的,因为JS众所周知是单线程的,所以很多人会认为在上面的例子中会先阻塞等待定时器执行完成后再执行下面的语句,但是这个也就是单线程的一个缺陷之一吧,为了解决这个问题,引入了异步机制。异步机制主要是利用一个我们平时很少去关注的一个知识点——浏览器的多线程。究竟什么是浏览器的多线程呢?

三、浏览器的多线程

 这里我们就来讲解一下,众所周知,JS是单线程的,但是对于浏览器来说JS的执行只不过是在浏览器众多现成中的一条,我们称之为JS引擎线程。而浏览器的其他线程则是通过JS引擎线程在执行到某个特定的功能之后指定给浏览器的对应线程。具体的原理详见图示:

JavaScript定时器实现的原理分析

 从这张图我们可以知道JS引擎线程首先执行回调函数块,然后是执行点击事件回调,接着是执行定时器的线程,最后在执行其他的线程。

以下面的代码我们来分析一下:

setTimeout("alert('定时器!')",0);
alert("测试")

首先JS线程读取到setTimeout定时器,这个时候就会执行浏览器的线程,然后跳过定时器继续执行,这个时候你就看到了弹出框的内容为测试,然后因为定时器的时间为0,所以一执行定时器线程就会即可将弹出框为定时器字样的任务添加到主线程(JS引擎线程)的队列之后,等待JS引擎的调用,这个时候我们看到的结果是先弹出测试,然后再弹出定时器

另外我们要注意在HTML5规范中规定定时器的定时时间不能小于4ms,如果是小于4ms,则默认为4ms,所以在这个例子中的0,默认的是4ms,但是这个在不通过的浏览器中的表现是不同的,但是这个一般在项目中是没有什么印象的,这个只是仅做了解即可。

 好的我们将上面的代码改写成这样,然后我们再来看看效果:

<script type="text/javascript">
  console.time("test");
  setTimeout("for(var i=0;i<1000;i++)console.log('定时器!');",1000);
  console.log("测试");
  console.timeEnd("test");
 </script>

    运行后的结果如下:

JavaScript定时器实现的原理分析

    这里有几个知识点:

1、console.time和console.timeEnd这两个方法是可以获取在其中间执行的语句所用的时间,从图中我们可以知道test执行的时间在1ms左右,然而定时器的定时时间是在1000ms左右所以这两个语句只能计算当前引擎的执行时间,换句话说就是在浏览器中的定时器模块的运行时间是这样是没法计算的

2、另外我们可以看到一个现象就是定时器在执行的时候不是一千个定时器的字样全都一次性的打印出来,而是几百几百的增加,这个是为什么呢?这里就涉及到了另外的一个问题,如果是定时器的时间到了,但是定时器中的任务没有执行完成这个时候会怎样?

我们上面说过就是定时器的时间到了的情况下,就会向JS引擎线程添加任务,不论任务里面的语句是否执行完成,都会像JS引擎线程队列中添加,但是剩下的未执行完成的语句怎么办呢?

程序执行到了定时器任务的时候,就会先把已经在定时器模块执行过的语句加载一次,然后是继续执行定时器模块的剩余语句。(定时器模块向JS引擎中添加的任务相当于就是C语言中的一个指针,指向的是定时器模块)

所以,setTimeout我们可以定义为:

在指定时间内, 将任务放入事件队列,等待js引擎空闲后被执行.

四、setInterval的使用

setInterval最基础的使用方法是直接当一个循环定时器使用,这里就不举例说明

对于setInterval(fn, 100)容易产生一个误区:并不是上一次fn执行完了之后再过100ms才开始执行下一次fn。 事实上,setInterval并不管上一次fn的执行结果,而是每隔100ms就将fn放入主线程队列,而两次fn之间具体间隔多久就不一定了,跟setTimeout实际延迟时间类似,和JS执行情况有关。具体的延迟效果与内存等因素有关。

五、定时器的可靠性

虽说定时器在大部分的情况下都是趋于稳定的,但是定时器在使用的时候也存在着一些误差

如下所示:

<script type="text/javascript">
  var time1 = new Date().getTime();
  setInterval(function(){
   var time2 = new Date().getTime();
   console.log("setInterval执行的差值时间:"+(time2-time1));
  },1000);
 </script>

运行的结果如下:

JavaScript定时器实现的原理分析

从图中我们基本可以看出定时器存在着一些小小的误差就比如第一次的运行时间为1001ms比我们设定的时间多出了1ms,所以得出结论:定时器不是完全的可靠的,存在极小的误差。这个还是在chrome浏览器上面测试的结果,换是在IE浏览器测试那又如何呢?

JavaScript定时器实现的原理分析

结果显示,在IE浏览器下面的误差更大

六、定时器的妙用

 定时器在项目中除了可以作为定时的作用外还可以用来做耗时代码的优化:

 我们假设有这样的一个场景,就是在某个页面中要渲染50万个节点,这个时候对于一般的项目中,直接渲染是不可取的,因为这个时候会占用过多的内存,导致浏览器出现了卡死的状态,用户误以为是页面卡死而  直接关闭浏览器或者杀死进程,即使是用户不关闭页面这样给用户的体验也是不好的,这个时候我们要怎样来解决这个问题呢,我们可以利用定时器来优化这个问题首先我们可以把50万个节点分成多组,每组渲染  的节点数不要过多,然后通过setInterval来进行循环这个既不阻塞JS引擎线程的运行,又不可以提高渲染的消耗时间。从而达到最终的优化渲染。

七、定时器使用注意事项

 如果是项目中有对个定时器的参与那么记得在一个定时器执行结束的时候记得要调用clearInterval或clearTimeout这两个方法来清除定时器,以免定时器之间互相干扰出现一些抓摸不定的现象

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,同时也希望多多支持三水点靠木!

Javascript 相关文章推荐
JS 页面自动加载函数(兼容多浏览器)
May 18 Javascript
JS实现鼠标滑过折叠与展开菜单效果代码
Sep 06 Javascript
JavaScript tab选项卡插件实例代码
Feb 23 Javascript
jQuery Validate 相关参数及常用的自定义验证规则
Mar 06 Javascript
BootStrap表单控件之复选框checkbox和单选择按钮radio
May 23 Javascript
浅谈react受控组件与非受控组件(小结)
Feb 09 Javascript
vue权限管理系统的实现代码
Jan 17 Javascript
vue学习笔记五:在vue项目里面使用引入公共方法详解
Apr 04 Javascript
小程序rich-text组件如何改变内部img图片样式的方法
May 22 Javascript
基于jQuery实现挂号平台首页源码
Jan 06 jQuery
Vue的v-model的几种修饰符.lazy,.number和.trim的用法说明
Aug 05 Javascript
JS实现超级好看的鼠标小尾巴特效
Dec 01 Javascript
原生js实现弹出层登录拖拽功能
Dec 05 #Javascript
详解Vue.js——60分钟组件快速入门(上篇)
Dec 05 #Javascript
原生js编写基于面向对象的分页组件
Dec 05 #Javascript
8 行 Node.js 代码实现代理服务器
Dec 05 #Javascript
浅谈Node.js:Buffer模块
Dec 05 #Javascript
微信小程序 Windows2008 R2服务器配置TLS1.2方法
Dec 05 #Javascript
详解jquery easyui之datagrid使用参考
Dec 05 #Javascript
You might like
PHP获取当前文件所在目录 getcwd()函数
2009/05/13 PHP
PHP操作文件的一些基本函数使用示例
2014/11/18 PHP
PHP扩展Swoole实现实时异步任务队列示例
2019/04/13 PHP
php获取目录下所有文件及目录(多种方法)(推荐)
2019/05/14 PHP
php和C#的yield迭代器实现方法对比分析
2019/07/17 PHP
用js实现层随着内容大小动态渐变改变 推荐
2009/12/19 Javascript
javascript代码加载优化方法
2011/01/30 Javascript
jquery选择器之层级过滤选择器详解
2014/01/27 Javascript
js实现简单的验证码
2015/12/25 Javascript
NodeJS远程代码执行
2016/08/28 NodeJs
js获取地址栏中传递的参数(两种方法)
2017/02/08 Javascript
关于jQuery库冲突的完美解决办法
2017/05/20 jQuery
jquery.rotate.js实现可选抽奖次数和中奖内容的转盘抽奖代码
2017/08/23 jQuery
vue自定义tap指令及tap事件的实现
2018/09/18 Javascript
详解小程序rich-text对富文本支持方案
2018/11/28 Javascript
js实现内置计时器
2019/12/16 Javascript
解决echarts echarts数据动态更新和dataZoom被重置问题
2020/07/20 Javascript
[01:14:34]DOTA2上海特级锦标赛C组资格赛#2 LGD VS Newbee第一局
2016/02/28 DOTA
[47:53]DOTA2上海特级锦标赛主赛事日 - 1 败者组第一轮#2COL VS Spirit
2016/03/02 DOTA
[00:56]PWL开团时刻DAY8——追追追追追!
2020/11/09 DOTA
下载给定网页上图片的方法
2014/02/18 Python
Python的Django框架中if标签的相关使用
2015/07/15 Python
python进阶_浅谈面向对象进阶
2017/08/17 Python
对pandas处理json数据的方法详解
2019/02/08 Python
python scipy卷积运算的实现方法
2019/09/16 Python
pytorch 实现在一个优化器中设置多个网络参数的例子
2020/02/20 Python
PyCharm 在Windows的有用快捷键详解
2020/04/07 Python
使用pymysql查询数据库,把结果保存为列表并获取指定元素下标实例
2020/05/15 Python
keras的siamese(孪生网络)实现案例
2020/06/12 Python
使用CSS3和Checkbox实现JQuery的一些效果
2015/08/03 HTML / CSS
总经理岗位职责
2013/11/09 职场文书
大二学生学年自我鉴定
2014/09/12 职场文书
党政领导班子群众路线对照检查材料思想汇报
2014/09/27 职场文书
公司领导班子群众路线四风问题对照检查材料
2014/10/02 职场文书
感动中国何玥观后感
2015/06/02 职场文书
读后感怎么写?书写读后感的基本技巧!
2019/12/10 职场文书