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 遮罩层和加载效果代码
Aug 01 Javascript
JS获取文本框,下拉框,单选框的值的简单实例
Feb 26 Javascript
javascript数字时钟示例分享
Apr 23 Javascript
JS对img标签进行优化使用onerror显示默认图像
Apr 24 Javascript
浅析javascript中function 的 length 属性
May 27 Javascript
jQuery基础语法实例入门
Dec 23 Javascript
基于jQuery实现表格的查看修改删除
Aug 01 Javascript
详解webpack进阶之loader篇
Aug 23 Javascript
three.js中3D视野的缩放实现代码
Nov 16 Javascript
webpack4 css打包压缩问题的解决
May 18 Javascript
vue操作下拉选择器获取选择的数据的id方法
Aug 24 Javascript
小程序按钮避免多次调用接口和点击方案实现(不用showLoading)
Apr 15 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
php下实现折线图效果的代码
2007/04/28 PHP
用php获取本周,上周,本月,上月,本季度日期的代码
2009/08/05 PHP
php checkdate、getdate等日期时间函数操作详解
2010/03/11 PHP
PHP中的array数组类型分析说明
2010/07/27 PHP
php判断是否为ajax请求的方法
2016/11/29 PHP
在Yii2特定页面如何禁用调试工具栏Debug Toolbar详解
2017/08/07 PHP
Jquery AJAX 用于计算点击率(统计)
2010/06/30 Javascript
jQuery选中select控件 无法设置selected的解决方法
2010/09/01 Javascript
JavaScript避免内存泄露及内存管理技巧
2014/09/05 Javascript
不依赖Flash和任何JS库实现文本复制与剪切附源码下载
2015/10/09 Javascript
JavaScript 函数的执行过程
2016/05/09 Javascript
BootStrap 模态框实现刷新网页并关闭功能
2017/01/04 Javascript
AngularJS中filter的使用实例详解
2017/08/25 Javascript
使用webpack-dev-server处理跨域请求的方法
2018/04/18 Javascript
[49:40]2018DOTA2亚洲邀请赛小组赛 A组加赛 TNC vs Newbee
2018/04/03 DOTA
Python在图片中添加文字的两种方法
2017/04/29 Python
Django中间件工作流程及写法实例代码
2018/02/06 Python
django之跨表查询及添加记录的示例代码
2018/10/16 Python
face++与python实现人脸识别签到(考勤)功能
2019/08/28 Python
详解Python中的GIL(全局解释器锁)详解及解决GIL的几种方案
2021/01/29 Python
西班牙多品牌鞋店连锁店:Krack
2018/11/30 全球购物
西班牙宠物用品和食品网上商店:Tiendanimal
2019/06/06 全球购物
Crabtree & Evelyn欧盟:豪华洗浴、身体和护发
2021/03/09 全球购物
通信工程专业个人找工作求职信范文
2013/09/21 职场文书
经济管理专业自荐信
2013/12/30 职场文书
思想作风纪律整顿心得体会
2014/09/04 职场文书
开展党的群众路线教育实践活动领导班子对照检查材料
2014/09/25 职场文书
幼儿园大班见习报告
2014/10/31 职场文书
2014年幼儿园安全工作总结
2014/11/10 职场文书
装饰施工员岗位职责
2015/04/11 职场文书
2016年敬老月活动总结
2016/04/05 职场文书
调研报告的主要写法
2019/04/18 职场文书
JavaScript嵌入百度地图API的最详细方法
2021/04/16 Javascript
React如何创建组件
2021/06/27 Javascript
vue使用wavesurfer.js解决音频可视化播放问题
2022/04/04 Vue.js
六个好看实用的 HTML + CSS 后台登录入口页面
2022/04/28 HTML / CSS