- 渲染进程所有运行在主线程上的任务都需要先添加到消息队列中,然后事件循环系统顺序执行消息队列
eg: 解析 DOM; 改变 web 大小, 重新布局; js 垃圾回收; 异步执行 js 代码 - 但是定时器的任务不能直接放置在消息队列中,他需要按照时间间隔来执行,因此,chrome 除了消息队列外,新增了个延时队列,在每次执行完任务后,执行延迟队列中的任务,计算出到期任务,依次执行
代码实现:
- 在每次新增一个定时器时,将定时器添加到 DelayTask 中
- 执行消息循环时,在当前任务执行完成时,执行延迟队列中的任务,ProcessTimerTask 函数会根据发起时间和延迟时间计算出到期的任务,然后依次执行这些到期的任务
- 查找延迟队列中的已到期任务执行
- 取消定时器: 从 delayed_incoming_queue 根据定时器 id 查找到定时器并删除
DelayedIncomingQueue delayed_incoming_queue;
// 当通过 JavaScript 调用 setTimeout 设置回调函数的时候,渲染进程将会创建一个回调任务,包含了回调函数 showName、当前发起时间、延迟执行时间
struct DelayTask{
int64 id;
CallBackFunction cbf;
int start_time;
int delay_time;
};
DelayTask timerTask;
timerTask.cbf = showName;
timerTask.start_time = getCurrentTime(); //获取当前时间
timerTask.delay_time = 200;//设置延迟执行时间
// 创建好回调任务之后,再将该任务添加到延迟执行队列中
delayed_incoming_queue.push(timerTask);
void ProcessTimerTask(){
//从delayed_incoming_queue中取出已经到期的定时器任务
//依次执行这些任务
}
//消息循环系统是 触发延迟队列的
TaskQueue task_queue;
void ProcessTask();
bool keep_running = true;
void MainTherad(){
for(;;){
//执行消息队列中的任务
Task task = task_queue.takeTask();
ProcessTask(task);
//执行延迟队列中的任务
ProcessTimerTask()
if(!keep_running) //如果设置了退出标志,那么直接退出线程循环
break;
}
}
使用 setTimeout 注意事项
- 当前任务执行过久,会影响定时器任务的执行
因为定时器延迟任务需要等到当前任务执行完毕之后才会执行,若当前任务执行时间过长,定时器会延迟执行 - setTimeout 存在嵌套调用的情况,系统设置的最短时间间隔为 4ms
function cb() {
setTimeout(cb, 0);
}
setTimeout(cb, 0);
-
未激活的页面(标签不是当前激活标签), setTimeout 执行最小间隔是 1000 毫秒,目的是优化后台页面的加载损耗以及降低耗电量
-
延时执行时间有最大值,Chrome、Safari、Firefox 都是以 32 个 bit 来存储延时值的,32bit 最大只能存放的数字是 2147483647 毫秒(大约 24.8 天),大于该时间会溢出,代码立即执行
-
使用 setTimeout 设置的回调函数中的 this 不符合直觉
如果被 setTimeout 推迟执行的回调函数是某个对象的方法,那么该方法中的 this 关键字将指向全局环境,而不是定义时所在的那个对象。// 这段代码在编译的时候,执行上下文中的 this 会被设置为全局 window,如果是严格模式,会被设置为 undefined。 var name = 1; var MyObj = { name: 2, showName: function () { console.log(this.name); }, }; setTimeout(MyObj.showName, 1000);
解决方式一:
放入匿名函数中执行//箭头函数 setTimeout(() => { MyObj.showName(); }, 1000); //或者function函数 setTimeout(function () { MyObj.showName(); }, 1000);
解决方式二:
使用 bindsetTimeout(MyObj.showName.bind(MyObj), 1000);
总结: setTimeout 底层实现
- 将定时器的基本信息存储在延时队列中,;
- 在事情循环中,执行完成当前的任务后,执行延迟队列;
- 在延迟队列中计算出已到期的任务,执行到期任务
- 在 settimeOut 中的任务,并不是总能实时执行
参考: 极客时间(浏览器工作原理与实践-李兵)