javascript 定时器工作原理分析


Posted in Javascript onDecember 03, 2016

setTimeout()

MDN对 setTimeout 的定义为:

在指定的延迟时间之后调用一个函数或执行一个代码片段。

语法

setTimeout 的语法非常简单,第一个参数为回调函数,第二个参数为延时的时间。函数返回一个数值类型的ID唯一标示符,此ID可以用作 clearTimeout 的参数来取消定时器:

var timeoutID = window.setTimeout(code, delay);

IE0+ 还支持回调参数的传入:

var timeoutID = window.setTimeout(func, delay, [param1, param2, ...]);

setInterval()

MDN 对 setInterval 的定义为:

周期性地调用一个函数(function)或者执行一段代码。

由于 setInterval 和 setTimeout 的用法一样,所以这里不再列出。

对第二个参数(delay)的说明

由于javascript 的事件循环机制,导致第二个参数并不代表延迟delay毫秒之后立即执行回调函数,而是尝试将回调函数加入到事件队列。实际上,setTimeout 和 setInterval 在这一点上处理又存在区别:

  • setTimeout:延时delay毫秒之后,啥也不管,直接将回调函数加入事件队列。
  • setInterval: 延时delay毫秒之后,先看看事件队列中是否存在还没有执行的回调函数(setInterval的回调函数),如果存在,就不要再往事件队列里加入回调函数了。

所以,当我们的代码中存在耗时的任务时,定时器并不会表现的如我们所想的那样。

通过一个例子来理解

下面的代码,本来希望能够在 100ms 和 200ms 的时候(也就是刚好等待 100ms)调用回调函数:

var timerStart1 = now();
setTimeout(function () {
 console.log('第一个setTimeout回调执行等待时间:', now() - timerStart1);

 var timerStart2 = now();
 setTimeout(function () {
  console.log('第二个setTimeout回调执行等待时间:', now() - timerStart2);
 }, 100);
}, 100);
// 输出:
// 第一个setTimeout回调执行等待时间: 106
// 第二个setTimeout回调执行等待时间: 107

这样的结果看上去正是我们所想的那样,但是一旦我们在代码中加入了耗时的任务时候,结果就不像我们所期望的那样了:

var timerStart1 = now();
setTimeout(function () {
 console.log('第一个setTimeout回调执行等待时间:', now() - timerStart1);

 var timerStart2 = now();
 setTimeout(function () {
  console.log('第二个setTimeout回调执行等待时间:', now() - timerStart2);
 }, 100);

 heavyTask(); // 耗时任务
}, 100);

var loopStart = now();
heavyTask(); // 耗时任务
console.log('heavyTask耗费时间:', now() - loopStart);

function heavyTask() {
 var s = now();
 while(now() - s < 1000) {
 }
}

function now () {
 return new Date();
}
// 输出:
// heavyTask耗费时间: 1015
// 第一个setTimeout回调执行等待时间: 1018
// 第二个setTimeout回调执行等待时间: 1000

两个 setTimeout 的等待事件由于耗时任务的存在不再是 100ms 了!我们来描述一下事情的经过:

  1. 首先,第一个耗时任务(heavyTask())开始执行,它需要大约 1000ms 才能执行完毕。
  2. 从耗时任务开始执行,过了 100ms, 第一个 setTimeout 的回调函数期望执行,于是被加入到事件队列,但是此时前面的耗时任务还没执行完,所以它只能在队列中等待,直到耗时任务执行完毕它才开始执行,所以结果中我们开的看到的是: 第一个setTimeout回调执行等待时间: 1018。
  3. 第一个 setTimeout 回调一执行,又开启了第二个 setTimeout, 这个定时器也是期望延时 100ms 之后能够执行它的回调函数。 但是,在第一个 setTimeout 又存在一个耗时任务,所有它的剧情跟第一个定时器一样,也等待了 1000ms 才开始执行。

可以用下面的图来概括:

javascript 定时器工作原理分析

再来看 setInterval 的一个例子:

var intervalStart = now();
setInterval(function () {
 console.log('interval距定义定时器的时间:', now() - loopStart);
}, 100);

var loopStart = now();
heavyTask();
console.log('heavyTask耗费时间:', now() - loopStart);

function heavyTask() {
 var s = now();
 while(now() - s < 1000) {
 }
}

function now () {
 return new Date();
}
// 输出:
// heavyTask耗费时间: 1013
// interval距定义定时器的时间: 1016
// interval距定义定时器的时间: 1123
// interval距定义定时器的时间: 1224

上面这段代码,我们期望每隔 100ms 就打出一条日志。相对于 setTimeout 的区别, setInterval 在准备把回调函数加入到事件队列的时候,会判断队列中是否还有未执行的回调,如果有的话,它就不会再往队列中添加回调函数。 不然,会出现多个回调同时执行的情况。

可以用下面的图来概括:

javascript 定时器工作原理分析

总结

上面对javascript定时器执行原理进行了简要的分析,希望能够帮助我们更深入的理解javascript。文中有描述不当的地方可以在评论中指出。

Javascript 相关文章推荐
JavaScript去除空格的几种方法
Oct 03 Javascript
一个关于javascript匿名函数的问题分析
Mar 30 Javascript
圣诞节Merry Christmas给博客添加浪漫的下雪效果基于jquery实现
Dec 27 Javascript
详解JavaScript中的blink()方法的使用
Jun 08 Javascript
require.js+vue开发微信上传图片组件
Oct 27 Javascript
值得分享的JavaScript实现图片轮播组件
Nov 21 Javascript
JavaScript制作弹出层效果
Dec 02 Javascript
angularjs实现的前端分页控件示例
Feb 10 Javascript
vue-quill-editor实现图片上传功能
Aug 08 Javascript
前端MVVM框架解析之双向绑定
Jan 24 Javascript
微信小程序获取当前位置和城市名
Nov 13 Javascript
JavaScript对象字面量和构造函数原理与用法详解
Apr 18 Javascript
JavaScript 最佳实践:帮你提升代码质量
Dec 03 #Javascript
简单理解Vue条件渲染
Dec 03 #Javascript
学习vue.js条件渲染
Dec 03 #Javascript
浅谈jQuery中Ajax事件beforesend及各参数含义
Dec 03 #Javascript
jquery 判断div show的状态实例
Dec 03 #Javascript
利用浮层使select不可选的实现方法
Dec 03 #Javascript
textarea 在浏览器中固定大小和禁止拖动的实现方法
Dec 03 #Javascript
You might like
Zend studio for eclipse中使php可以调用mysql相关函数的设置方法
2008/10/13 PHP
ThinkPHP分组下自定义标签库实例
2014/11/01 PHP
Laravel 5框架学习之表单
2015/04/08 PHP
PHP函数超时处理方法
2016/02/14 PHP
php ci 获取表单中多个同名input元素值的代码
2016/03/25 PHP
php实现微信企业号支付个人的方法详解
2017/07/26 PHP
JS写的贪吃蛇游戏(个人练习)
2013/07/08 Javascript
javascript中数组array及string的方法总结
2014/11/28 Javascript
jQuery DOM插入节点操作指南
2015/03/03 Javascript
JavaScript使用pop方法移除数组最后一个元素用法实例
2015/04/06 Javascript
js+html5通过canvas指定开始和结束点绘制线条的方法
2015/06/05 Javascript
JS操作COOKIE实现备忘记录的方法
2016/04/01 Javascript
JavaScript的模块化开发框架Sea.js上手指南
2016/05/12 Javascript
fullpage.js全屏滚动插件使用实例
2016/09/06 Javascript
JavaScript求一个数组中重复出现次数最多的元素及其下标位置示例
2018/07/23 Javascript
vue实现日历备忘录功能
2020/09/24 Javascript
[00:12]2018DOTA2亚洲邀请赛 Sccc亮相SOLO赛,今年他又会有什么样的战绩?
2018/04/06 DOTA
初学python数组的处理代码
2011/01/04 Python
Python 2与Python 3版本和编码的对比
2017/02/14 Python
python中aioysql(异步操作MySQL)的方法
2019/04/11 Python
int在python中的含义以及用法
2019/06/27 Python
如何在python中写hive脚本
2019/11/08 Python
Pytorch使用MNIST数据集实现CGAN和生成指定的数字方式
2020/01/10 Python
python通过安装itchat包实现微信自动回复收到的春节祝福
2020/01/19 Python
浅谈python之自动化运维(Paramiko)
2020/01/31 Python
Python+OpenCV检测灯光亮点的实现方法
2020/11/02 Python
pycharm + django跨域无提示的解决方法
2020/12/06 Python
英国发展最快的在线超市之一:Click Marketplace
2021/02/15 全球购物
学校综治宣传月活动总结
2014/07/02 职场文书
房产销售独家委托书范本
2014/10/01 职场文书
夫妻双方自愿离婚协议书
2014/10/24 职场文书
初中政治教师教学反思
2016/02/23 职场文书
安装pytorch时报sslerror错误的解决方案
2021/05/17 Python
教你用Java Swing实现自助取款机系统
2021/06/11 Java/Android
十大动画制作软件,Adobe产品上榜两款,第一是行业标准软件
2022/03/18 杂记
详解Flutter网络请求Dio库的使用及封装
2022/04/14 Java/Android