定时器setTimeout

169 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第32天,点击查看活动详情

setTimeout

是什么

setTimeout是定时器,在实际开发中是很常用的。用来指定某个函数多少秒后执行。

我们都知道,虽然我们定义了具体的毫秒数,但未必就是在定义的毫秒数结束后就立即执行了,因为我们每一个任务需要放入队列中等待后执行。

返回值

它会返回一个整数,表示的是定时器的编号。我们可以用这个编号来取消定时器。

let timerId = setTimeout(() => {
  console.log('juejin...')
}, 300)
console.log(timerId) // 14100

如果在定时器被执行之前需要取消执行定时器函数,可以执行clearTimeout函数。

clearTimeout(timerId)

在浏览器中是如何实现的

众所周知,JavaScript是单线程工作的。在执行多个任务时,例如接下来需要执行:

  • 一段异步JS代码
  • 添加或删除一个DOM节点
  • 改变浏览器窗口大小

这三个任务会被放入消息队列排队执行,当然如果是异步操作,事件循环系统也会不断把任务加入到消息队列中循环执行。

任务是怎么执行的我们清楚了,那定时器函数为什么会在延迟时间后被执行呢?我们我们把延迟任务放入刚才说的消息队列中,那时间误差肯定会很大,特别是遇到同步任务比较费时的任务,那肯定被延误了,所谓的定时执行也无从谈起。

其实在浏览器中除了普通的消息队列之外,还有另外一个比较特殊的队列:延迟队列。里面都是存的需要延迟的任务列表。包括定时器和Chromium内部一些需要延迟的任务。

如下代码:

延迟任务都是放到delayed_incoming_queue队列中,ProcessTimerTask函数执行的就是定时或延迟任务,根据配置的延迟时间到了之后就会执行。

void ProcessTimerTask(){
  // 从delayed_incoming_queue中取出已经到期的定时器任务
  // 依次执行这些任务
}
TaskQueue task_queue;
void ProcessTask();
bool keep_running = true;
void MainTherad(){
  for(;;){
    //执行消息队列中的任务
    Task task = task_queue.takeTask();
    ProcessTask(task);
    //执行延迟队列中的任务
    ProcessDelayTask()
    if(!keep_running) //如果设置了退出标志,那么直接退出线程循环
        break; 
  }
}