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 相关文章推荐
JavaScript中变量提升 Hoisting
Jul 03 Javascript
收集json解析的四种方法分享
Jan 17 Javascript
jQuery提示插件alertify使用指南
Apr 21 Javascript
JS实现将数字金额转换为大写人民币汉字的方法
Aug 02 Javascript
AngularJS入门教程引导程序
Aug 18 Javascript
详解js产生对象的3种基本方式(工厂模式,构造函数模式,原型模式)
Jan 09 Javascript
JavaScript控制输入框中只能输入中文、数字和英文的方法【基于正则实现】
Mar 03 Javascript
微信小程序商城项目之商品属性分类(4)
Apr 17 Javascript
JS简单实现点击按钮或文字显示遮罩层的方法
Apr 27 Javascript
vue.js中Vue-router 2.0基础实践教程
May 08 Javascript
Vue与Node.js通过socket.io通信的示例代码
Jul 25 Javascript
详解vue移动端项目的适配(以mint-ui为例)
Aug 17 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
调频问题解答
2021/03/01 无线电
php程序内部post数据的方法
2015/03/31 PHP
php中smarty模板条件判断用法实例
2015/06/11 PHP
Thinkphp3.2简单解决多文件上传只上传一张的问题
2017/09/26 PHP
基于jquery实现后台左侧菜单点击上下滑动显示
2013/04/11 Javascript
把jquery 的dialog和ztree结合实现步骤
2013/08/02 Javascript
动态改变div的z-index属性的简单实例
2013/08/08 Javascript
js动态拼接正则表达式的两种方法
2014/03/04 Javascript
浏览器窗口大小变化时使用resize事件对框架不起作用的解决方法
2014/05/11 Javascript
js使用栈来实现10进制转8进制与取除数及余数
2014/06/11 Javascript
jQuery实现下拉框选择图片功能实例
2015/08/08 Javascript
jQuery添加options点击事件并传值实例代码
2016/05/18 Javascript
基于jQuery实现选项卡效果
2017/01/04 Javascript
vue2.0 中#$emit,$on的使用详解
2017/06/07 Javascript
vue-router单页面路由
2017/06/17 Javascript
浅谈vue的踩坑路
2017/08/31 Javascript
小程序rich-text组件如何改变内部img图片样式的方法
2019/05/22 Javascript
Vue项目中使用WebUploader实现文件上传的方法
2019/07/21 Javascript
如何使用webpack打包一个库library的方法步骤
2019/12/18 Javascript
[01:29]Ti4循环赛第三日精彩回顾
2014/07/13 DOTA
[02:37]TI8勇士令状不朽珍藏II视频展示
2018/06/23 DOTA
Python实现的批量下载RFC文档
2015/03/10 Python
Python实现读取Properties配置文件的方法
2018/03/29 Python
python 文件转成16进制数组的实例
2018/07/09 Python
在ubuntu16.04中将python3设置为默认的命令写法
2018/10/31 Python
Python Django简单实现session登录注销过程详解
2019/08/06 Python
解决python多行注释引发缩进错误的问题
2019/08/23 Python
详解BeautifulSoup获取特定标签下内容的方法
2020/12/07 Python
css3实现小箭头各种图形效果
2020/07/08 HTML / CSS
兰蔻加拿大官方网站:Lancome加拿大
2016/08/05 全球购物
银行会计业务的个人自我评价
2013/11/02 职场文书
摄影专业毕业生求职信
2014/08/05 职场文书
拆迁委托协议书
2014/09/15 职场文书
客房领班岗位职责
2015/02/11 职场文书
兼职安全员岗位职责
2015/02/15 职场文书
在容器中使用nginx搭建上传下载服务器
2022/05/11 Servers