今天的一道面试题(20) - 详细讲述一下浏览器的循环系统

先综述一下:
浏览器在执行js时有一个渲染主线程, 但是其他IO线程, 网络进程等等也可以向主线程发送一些任务, 必须输入输出, 下载等等, 就引入了消息队列

消息队列中存放的是其他线程和进程发送过来的任务, 每一个任务都是一个宏任务, 浏览器在执行时, 每次从队列首部取出最老的一个任务开始执行

setTimeout异步任务 : 存放在延迟消息队列中, 在浏览器执行完消息队列中的一个最老的任务后, 会取出延迟消息队列中到期的延迟任务, 开始执行. (所以setTimeout可能会有延迟, 不一定到期之后就会立刻被执行)

微任务就是在消息队列中的每个宏任务被执行时, 浏览器都会为其创建该宏任务对应的微任务队列, 宏任务执行完毕会执行其对应的微任务. 所以微任务的执行时间也会影响到宏任务的总体执行时长

下面详细讲述( 浏览器工作原理与实践 的笔记)

消息队列和事件机制

渲染进程下有一个渲染主线程主要来执行任务, 还可以IO线程来添加任务

一个比较好的接收其他线程发送的消息的线程模型就是 - 消息队列

消息队列

在这里插入图片描述

流程如下:

  1. 添加一个消息队列;
  2. IO 线程中产生的新任务添加进消息队列尾部;
  3. 渲染主线程会循环地从消息队列头部中读取任务,执行任务。

IO线程也用来接收其他进程发送过来的网络消息, 比如网络资源下载完成等

消息队列中的任务类型

消息队列中的任务类型主要有: 输入事件(鼠标滚动、点击、移动)、微任务、文件读写、WebSocket、JavaScript 定时器等等。

除此之外,消息队列中还包含了很多与页面相关的事件,如 JavaScript 执行、解析 DOM、样式计算、布局计算、CSS 动画等。

以上这些事件都是在主线程中执行的,所以在编写 Web 应用时,你还需要衡量这些事件所占用的时长,并想办法解决单个任务占用主线程过久的问题。

微任务的引入

在消息队列中的所有任务都是单线程的, 必须要等前一个任务完成之后才能去执行下一个任务.

这个时候引入了微任务, 消息队列中的每个任务都是一个宏任务, 每个宏任务都包含一个微任务队列. 如果有高优先级的任务可以放在某个宏任务对应的微任务队列中

setTimeout的实现

setTimeout是一个定时器, 指定多少毫秒之后执行. 如果我们想要执行一段异步任务, 不能立刻将其加入任务队列, 而是要在指定时间之后将其加入到任务队列

延迟消息队列

在 Chrome 中除了正常使用的消息队列之外,还有另外一个消息队列,这个队列中维护了需要延迟执行的任务列表,包括了定时器和 Chromium 内部一些需要延迟执行的任务

所以当通过 JavaScript 创建一个定时器时,渲染进程会将该定时器的回调任务添加到延迟队列中

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; 
  }
}

在上段代码中,处理完消息队列中的一个任务之后,就开始执行 ProcessDelayTask 函数。ProcessDelayTask 函数会根据发起时间和延迟时间计算出到期的任务,然后依次执行这些到期的任务。等到期的任务执行完成之后,再继续下一个循环过程。通过这样的方式,一个完整的定时器就实现了。

setTimeout的问题

当前任务执行时间过久, 影响延迟到期定时器任务的执行

function bar() {
   
    console.log('bar')
}
function foo() {
   
    setTimeout(bar, 0);
    for (let i = 0; i < 5000; i++) {
   
        let i = 5+8+8+8
        console.log(i)
    }
}
foo()

即使setTimeout的延迟时间是0, 但是由于消息队列中的任务for循环执行时间太久会阻碍延迟消息任务的执行, for循环执行大约花费500ms, 所以setTimeout会在500ms后执行

如果 setTimeout 存在嵌套调用,那么系统会设置最短时间间隔为 4 毫秒

function cb() {
    setTimeout(cb, 0); }
setTimeout(
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值