事件循环

98 阅读3分钟

浏览器的进程模型

什么是进程

程序在运行时需要一定的内存空间,我们可以简单的把这块内存空间理解为进程。

不同程序之间的进程是相互独立的。

什么是线程

在进程中来执行程序的就是线程。

一个进程至少需要有一个线程,所以在程序启动时会自动创建一个线程来运行代码, 我们称之为 主线程

如果程序需要同时执行多段代码,主线程 就会启动更多的线程来执行,因此就会存在多个线程。

浏览器有哪些进程及线程

由于浏览器的工作原理非常复杂,因此浏览器是一个 多进程多线程 的应用程序。

浏览器主要有三个进程:

  • 浏览器进程:主要负责界面(非页面)显示、用户交互、子进程管理等。浏览器进程内部会启动多个线程处理不同的任务。
  • 网络进程:负责加载网络资源。网络进程内部会启动多个线程来处理不同的网络任务。
  • 渲染进程:负责执行 HTML、CSS、JS 代码。渲染进程启动后,只会开启一个线程,这个线程就是渲染主线程。

浏览器进程跟网络进程都是多线程的,而渲染进程只会开启一个线程,这是因为浏览器无法做到在渲染进程中开启多个线程。

渲染主线程的工作原理

渲染主线程是浏览器中最繁忙的线程,需要它处理的任务包括但不限于:

  • 解析 HTML
  • 解析 CSS
  • 计算样式
  • 布局
  • 处理图层
  • 每秒把页面画 60 次
  • 执行全局 JS 代码
  • 执行事件处理函数
  • 执行计时器的回调函数
  • ......

这么多的任务需要处理,如何去调度成了一个关键问题。

渲染主线程采取的思路如下:

  1. 在最开始的时候,渲染主线程会进入一个无限循环。
  2. 每一次循环会检查消息队列中是否有任务存在。如果有,就取出第一个任务执行,执行完一个后进入下一次循环;如果没有,则进入休眠状态。
  3. 其他所有线程(包括其他进程的线程)可以随时向消息队列添加任务。新任务会加到消息队列的末尾。在添加新任务时,如果主线程是休眠状态,则会将其唤醒以继续循环拿取任务。

这个过程,就被称之为事件循环(消息循环) 202208092230847.png

什么是异步

代码在执行过程中,有一些任务是无法立即执行的,例如:

  • 定时器相关的任务:setTimeoutsetInterval

  • 网络请求相关的任务:XHRFetch

  • 用户交互相关的任务:addEventListener

在事件循环过程中,如果遇到这些任务时浏览器采取同步的方式,即等待前一个任务结束再去执行下一个任务,那么渲染主线程就会发生阻塞。

所以浏览器选择采取 异步 的方式来执行任务,根据任务的类型来将任务放到不同的消息队列中。

任务的优先级

每个任务有不同的类型,同类型的任务必须在同一个队列,不同的任务可以属于不同的队列。

任务没有优先级,在消息队列中先进先出,但消息队列是有优先级的

在目前的浏览器中,主要包含了下面的队列:

  • 延时队列:用于存放计时器到达后的回调任务,优先级「中」
  • 交互队列:用于存放用户操作后产生的事件处理任务,优先级「高」
  • 微队列:用户存放需要最快执行的任务,优先级「最高」

可以通过 Promise.resolve().then(函数) 添加任务到微队列。