事件循环(消息循环)

浏览器的进程模型

进程:程序运行需要它自己专属的内存空间,可以把这块内存空间简单的理解为进程

线程:有了进程之后,就可以运行程序的代码,运行代码的'人'称之为'线程',一个进程至少有一个线程,所以在程序开启后会自动创建一个线程来运行代码,该线程称之为主线程

浏览器有哪些进程和线程

浏览器是一个多进程多线程的应用程序

浏览器内部工作极其复杂

为了避免相互影响,为了减少连环崩溃的几率,当启动浏览器后,它会自动启动多个进程

其中最主要的进程有:

  1. 浏览器进程

负责浏览器界面的展示、用户交互、子进程管理等。浏览器进程内部会启动多个线程处理不同的任务

  1. 网络进程

负责加载网络资源。网络进程内部会启动多个线程来处理不同的网络任务

  1. 渲染进程

渲染进程启动后,会开启一个渲染主线程,主线程负责执行 HTML、CSS、JS代码。

默认情况下,浏览器会为每个标签页开启一个新的渲染进程,以保证不同标签页之间相互不影响

渲染主线程是如何工作的

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

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

为什么渲染进程不适用多个线程来处理这些事情?浏览器也没有办法,至于为什么,自己查吧!!!

要处理这么多任务,主线程遇到一个前所未有的问题,如何调度任务? 比如:

  • 正在执行一个JS函数,执行到一半了,用户点击了按钮,该立即去执行点击事件的处理函数吗?
  • 正在执行一个JS函数,执行到一半,某个计时器到时间了,该立即去执行它的回调吗?
  • 浏览器进程通知"用户点击了按钮",同时,某个计时器也到达了时间,该如何处理
  • .....

那么解决方法就是排队

  1. 在最开始的时候,渲染主线程会进入一个无限循环
  2. 每一次循环会检查消息队列中是否有任务存在。如果有,就取出第一个任务执行,执行完一个后进入下一次循环,没有任务,就休息
  3. 其他所有的线程,可以随时向消息队列添加任务,如果主线程是休眠的,那么就会唤醒主线程

何为异步

代码在执行过程中,会遇到一些无法立即执行的任务,比如:

  • 计时完成后需要执行的任务 -- setTimeout setInterval
  • 网络通信完成后需要执行的任务 -- XHR Fetch
  • 用户操作后需要执行的任务 -- addEventListener

如果让渲染主线程等待这些任务的时机到达,就会导致主线程长期处于'阻塞'状态,从而导致浏览器'卡死'

渲染主线程承担着极其重要的工作,无论如何都不能阻塞

因此,浏览器选择了异步

任务有优先级吗?

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

消息队列有优先级

根据W3C的最新解释:(过去是,任务有宏任务(普通任务)、微任务(vip任务),现在两个队列根本搞不定现在的复杂的任务场景,因此抛弃了宏队列的说法)

  • 每个任务都有一个任务类型,同一个类型的任务必须在一个队列,不同类型的任务可以分属不同的队列。在一次事件循环中,浏览器可以根据实际情况从不同的队列中取出任务执行。
  • 浏览器必须准备好一个微队列,微队列中的任务优先所有其他任务执行

在目前chrome的实现中,至少包含了以下几个队列:

  • 延时队列:用户存放计时器到达后的回调任务,优先级【中】
  • 交互队列:用于存放用户操作后产生的事件处理,优先级【高】
  • 微队列:优先级【高】

添加任务到微队列的主要方式是 Promise、MutationObserver

比如:

js 复制代码
Promise.resolve().then(函数) // 这个then中的函数直接到微队列

面试题:

  1. 说一下JS的事件循环

事件循环又叫消息循环,是浏览器渲染主线程的工作方式

在Chrome源码中,它开启一个不会结束的for循环,每次循环从消息队列中取出一个任务执行,而其他线程只需要在合适的时候将任务加入到队列末尾即可。

过去把消息队列简单的分为红任务,微任务,但这种说法已经无法满足复杂的浏览器环境,取而代之的更灵活多变的方式

根据W3C的解释,每个任务有不同的类型,同类型的任务必须在同一个队列,不同的任务可以属于不同的队列,不同的任务队列有不同的优先级,在一次事件循环中,由浏览器自行决定取哪一个队列的任务。

但浏览器必须有一个微队列,微队列的任务的优先级为最高,必须优先调度执行

  1. JS中的计时器能做到精确计时吗?为啥?

不行,因为:

  1. 计算机硬件没有原子钟,无法做到
  2. 操作系统的计时函数本身就有少量偏差,js的计时器是调用操作系统的函数
  3. 按照W3C的标准,浏览器实现计时器时,如果嵌套层级超过5层,就会带有4毫秒的最少时间
  4. 受事件循环的影响,计时器的回调函数只能在主线程空闲时运行,因此带来了偏差
  1. 如何理解js的异步

js是单线程的,因为它是运行在浏览器渲染主线程里面的

而渲染主线程要做的事很多,如:执行JS,解析HTML,渲染页面等

因此如果是同步,那么就有可能会导致阻塞,这样就会导致消息队列中的其他任务无法执行

这样一来,就会导致页面无法及时更新造成卡死现象,以及导致繁忙的主线程白白浪费时间

所以浏览器就使用了异步,具体就在当某些任务发生时,主线程将任务交给其他线程处理,自身立即结束此任务

当其他线程完成时,将事先传递的回调函数打包成任务,加入消息队列中,等到主线程调度执行

从而实现浏览器的永不阻塞

相关推荐
腾讯TNTWeb前端团队1 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰4 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪4 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪5 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy5 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom6 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom6 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom6 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom6 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom6 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试