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 相关文章推荐
静态的动态续篇之来点XML
Aug 15 Javascript
jQuery版Tab标签切换
Mar 16 Javascript
情人节专属 纯js脚本1k大小的3D玫瑰效果
Feb 11 Javascript
Javascript的各种节点操作实例演示代码
Jun 27 Javascript
第四章之BootStrap表单与图片
Apr 25 Javascript
JS+html5 canvas实现的简单绘制折线图效果示例
Mar 13 Javascript
单击按钮发送验证码,出现倒计时的简单实例
Mar 17 Javascript
很棒的vue弹窗组件
May 24 Javascript
详解webpack 多页面/入口支持&amp;公共组件单独打包
Jun 29 Javascript
JS实现的按钮点击颜色切换功能示例
Oct 19 Javascript
Postman动态获取返回值过程详解
Jun 30 Javascript
vuex的使用步骤
Jan 06 Vue.js
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
PHP 工厂模式使用方法
2010/05/18 PHP
thinkphp查询,3.X 5.0方法(亲试可行)
2017/06/17 PHP
laravel5.1框架基础之Blade模板继承简单使用方法分析
2019/09/05 PHP
Swoole源码中如何查询Websocket的连接问题详解
2020/08/30 PHP
一个对于Array的简单扩展
2006/10/03 Javascript
HTML中Select不用Disabled实现ReadOnly的效果
2008/04/07 Javascript
ext for eclipse插件安装方法
2008/04/27 Javascript
在javaScript中关于submit和button的区别介绍
2013/10/20 Javascript
JavaScript点击按钮后弹出透明浮动层的方法
2015/05/11 Javascript
如何用javascript计算文本框还能输入多少个字符
2015/07/29 Javascript
jQuery+css3实现Ajax点击后动态删除功能的方法
2015/08/10 Javascript
用Angular实时获取本地Localstorage数据,实现一个模拟后台数据登入的效果
2016/11/09 Javascript
ES6中参数的默认值语法介绍
2017/05/03 Javascript
详解JS中的柯里化(currying)
2017/08/17 Javascript
Vue resource三种请求格式和万能测试地址
2018/09/26 Javascript
js+css实现全屏侧边栏
2020/06/16 Javascript
Vue和React有哪些区别
2020/09/12 Javascript
[09:13]2014DOTA2国际邀请赛 中国区预选赛coser表演
2014/05/23 DOTA
简单的连接MySQL与Python的Bottle框架的方法
2015/04/30 Python
Python将图片批量从png格式转换至WebP格式
2020/08/22 Python
python的Tqdm模块的使用
2018/01/10 Python
利用Python yagmail三行代码实现发送邮件
2018/05/11 Python
Python实现的json文件读取及中文乱码显示问题解决方法
2018/08/06 Python
py-charm延长试用期限实例
2019/12/22 Python
详解pandas中iloc, loc和ix的区别和联系
2020/03/09 Python
使用python-Jenkins批量创建及修改jobs操作
2020/05/12 Python
CSS3 对过渡(transition)进行调速以及延时
2020/10/21 HTML / CSS
详解使用HTML5 Canvas创建动态粒子网格动画
2016/12/14 HTML / CSS
俄罗斯最大的香水和化妆品网上商店:Randewoo
2020/11/05 全球购物
消防安全汇报材料
2014/02/08 职场文书
岗位安全生产责任书
2014/07/28 职场文书
社会实践活动总结
2015/02/05 职场文书
社区公民道德宣传日活动总结
2015/03/23 职场文书
国富论读书笔记
2015/06/26 职场文书
Node与Python 双向通信的实现代码
2021/07/16 Javascript
Python OpenCV实现图像模板匹配详解
2022/04/07 Python