# 前言
- 一个定时器,指定某函数多少ms后执行。
- return 一个整数代表定时器的编号。
- 通过编号可以取消定时器。
function foo() {
console.log('hi~')
}
// 1秒后调用foo函数
let timer = setTimeout(foo, 1000)
// clearTimeout(timer)
1
2
3
4
5
6
2
3
4
5
6
# 浏览器如何实现setTimeout
渲染进程中主线程去执行任务的机制:
- 任务添加到消息队列
- 主线程依赖事件循环去按照顺序执行消息队列中的任务
常见的事件(事件其实就可以概括认为是任务,循环监听事件发生,一旦发生,产生任务进入消息队列,等待执行):
- 接收到HTML文档数据 => 渲染引擎将“解析DOM”事件(任务)添加到消息队列。
- 用户改变浏览器窗口大小 => 渲染引擎将“重新布局”的事件添加到消息队列。
- 触发JavaScript引擎垃圾回收机制 => 渲染引擎将“垃圾回收”任务添加到消息队列中。
注意:为什么JavaScript垃圾回收的任务被渲染引擎接手了? - 执行一段异步JavaScript代码 => 将执行任务添加到消息队列中。
# 定时器的特殊性
定时器需要指定时间间隔然后调用,而消息队列中的任务是按照顺序执行的,所以定时器的回调函数不能直接添加到消息队列中。
Chrome中除了正常使用的消息队列,额外还有一个消息队列,该队列中维护了需要延迟执行的任务(定时器、Chrome内部延迟任务)。每当JavaScript创建一个定时器,渲染进程将该定时器的回调函数任务添加到延迟队列中。
# 事件循环系统执行延迟队列
- 每当处理完消息队列中的一个任务之后;
- 到延迟队列中根据发起时间、延迟时间计算出到期任务,依次执行到期任务;
- 到期任务执行完成,继续上述的下一个循环;
# 定时器删除
设置定时器,JavaScript引擎会返回一个定时器的ID。
一般情况,当一个定时器的任务暂未执行,是可以取消的,调用clearTimeout函数,并传入需要取消的定时器的 ID。
浏览器内部如何实现:根据ID从延迟队列中找到对应任务,将其删除即可。
# 使用setTimeout注意点
- setTimeout设置的回调函数中的this。
如果setTimeout延迟执行的回调函数是某对象的方法,那么该方法中的this关键字将指向全局环境。
let name= 1;
let obj = {
name: 2,
showName: function(){
console.log(this.name);
}
}
// 这段代码在编译的时候,执行上下文中的this会被设置为全局window对象
// log 1
setTimeout(obj.showName,1000)
// ====解决this指向====
// 箭头函数包装
setTimeout(() => {
obj.showName()
}, 1000);
// function函数包装
setTimeout(function() {
obj.showName();
}, 1000)
// bind绑定
setTimeout(obj.showName.bind(obj), 1000)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- 当前任务执行过久,会影响到期的定时器任务执行。
function timer() {
console.log('timer run')
}
function test() {
// timer回调任务放入延迟队列等待执行
// 无法立即执行,需要等待当前任务执行完成
setTimeout(timer, 0);
// 当前任务执行该循环消耗时长肯定是不止0ms的
// 所以影响到setTimeout 0ms后执行
for (let i = 0; i < 10000; i++) {
let j = 1 * 2
console.log(j)
}
}
test()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- setTimeout嵌套调用,系统会设置最短时间间隔为4ms。
function cb() { setTimeout(cb, 0); }
setTimeout(cb, 0);
1
2
2
Chrome中,定时器被嵌套调用5次以上,系统会判断该函数方法被阻塞了,如果定时器的调用时间间隔小于4ms,那么浏览器会将每次调用的时间间隔设置为4ms。
所以一些实时性较高的需求不太适合使用setTimeout,用setTimeout来实现 JavaScript动画不是很合适。
- 未激活的页面,setTimeout执行最小间隔是1000ms。
如果tab标签不是当前激活的标签,那么定时器最小的时间间隔是1000ms。 目的是优化后台页面的加载损耗以及降低耗电量,要注意下。 - 延时执行时间有最大值(2147483647毫秒 约24.8天)。
Chrome、Safari、Firefox都是以32个bit来存储延时值的,32bit最大只能存放的数字是 2147483647毫秒,如果setTimeout设置的延迟值大于2147483647毫秒(约 24.8 天)时就会溢出,这导致定时器会被立即执行。
# 总结
- 除了一般的消息队列,浏览器增加了延时队列,支持定时器的实现。
- 消息队列排队和系统级别的限制,setTimeout设置的回调任务并非可以总是按照给定的时间被执行,不能满足一些实时性要求较高的需求。
- 使用setTimeout,要注意其给定延时时间的一些特殊情况,this的指向。