setTimeout 实现原理

  • 渲染进程所有运行在主线程上的任务都需要先添加到消息队列中,然后事件循环系统顺序执行消息队列
    eg: 解析 DOM; 改变 web 大小, 重新布局; js 垃圾回收; 异步执行 js 代码
  • 但是定时器的任务不能直接放置在消息队列中,他需要按照时间间隔来执行,因此,chrome 除了消息队列外,新增了个延时队列,在每次执行完任务后,执行延迟队列中的任务,计算出到期任务,依次执行

代码实现:

  1. 在每次新增一个定时器时,将定时器添加到 DelayTask 中
  2. 执行消息循环时,在当前任务执行完成时,执行延迟队列中的任务,ProcessTimerTask 函数会根据发起时间和延迟时间计算出到期的任务,然后依次执行这些到期的任务
  3. 查找延迟队列中的已到期任务执行
  4. 取消定时器: 从 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 注意事项

  1. 当前任务执行过久,会影响定时器任务的执行
    因为定时器延迟任务需要等到当前任务执行完毕之后才会执行,若当前任务执行时间过长,定时器会延迟执行
  2. setTimeout 存在嵌套调用的情况,系统设置的最短时间间隔为 4ms
function cb() {
  setTimeout(cb, 0);
}
setTimeout(cb, 0);
  1. 未激活的页面(标签不是当前激活标签), setTimeout 执行最小间隔是 1000 毫秒,目的是优化后台页面的加载损耗以及降低耗电量

  2. 延时执行时间有最大值,Chrome、Safari、Firefox 都是以 32 个 bit 来存储延时值的,32bit 最大只能存放的数字是 2147483647 毫秒(大约 24.8 天),大于该时间会溢出,代码立即执行

  3. 使用 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);
    

    解决方式二:
    使用 bind

    setTimeout(MyObj.showName.bind(MyObj), 1000);
    

    总结: setTimeout 底层实现

  • 将定时器的基本信息存储在延时队列中,;
  • 在事情循环中,执行完成当前的任务后,执行延迟队列;
  • 在延迟队列中计算出已到期的任务,执行到期任务
  • 在 settimeOut 中的任务,并不是总能实时执行

参考: 极客时间(浏览器工作原理与实践-李兵)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值