【浏览器工作原理】8. 浏览器中的页面循环机制是如何处理的

521

引言

我们知道每个渲染进程都有一个主线程,既要处理DOM,又要计算样式、处理布局及JavaScript任务,要让这么多不同类型的任务在主线程中有条不紊地执行,需要一个系统来调度这些任务,也就是本文要讲的事件循环和消息队列

事件循环机制

为了方便理解,我们从简单的任务场景依次到复杂场景来说明:

    1. 使用单线程处理安排好的任务: 所有任务代码按照顺序写进主线程,按照顺序依次执行,执行完成线程自动退出。

704b80a7f5afc17b970713b8faf45916.png

  • 2. 在线程运行过程中处理新任务:事件循环机制 并不是所有任务都是执行前统一安排好的,为了在线程运行过程中能接收并执行新的任务,需要引入事件循环机制。
    • 循环机制:通过一个for循环语句来监听是否有新的任务
    • 引入事件:可以在线程运行过程中,等待用户输入,接收到输入的信息后线程会被激活

image.png

  • 3. 处理其他线程发送过来的任务:消息队列 3.1 引入消息队列(一种数据结构,存放要执行的任务,符合先进先出特点) 3.2 IO线程中产生的新任务添加进消息队列尾部 3.3 渲染主线程会循环地从消息队列头部中读取任务,执行任务

image.png

    1. 处理其他进程发送过来的任务: 使用消息队列实现了线程之间的消息通信,跨进程的任务如何处理呢? 渲染进程专门有个IO线程用来接收其他进程的消息,接收消息后会组装成任务发送给渲染主线程

image.png

消息队列

    1. 任务类型 宏任务:消息队列中的任务,例如:
  • 渲染事件(如解析DOM、计算布局、绘制);

  • 用户交互事件;JavaScript脚本执行事件;

  • 网络请求完成

  • 文件读写完成事件 宏任务时间粒度比较大,对于高实时性需求不太符合。渲染进程内部会维护多个消息队列:延迟执行队列(setTimeout)和普通的消息队列

微任务:需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前的任务。 微任务队列是给v8引擎内部使用的,无法通过JavaScript直接访问。

  1. 产生方式:
  • 使用MutationObserver监控某个DOM节点,当修改节点时就会产生DOM变化记录的微任务
  • 使用Promise,当调用Promise.resolve()或Promise.reject()时

执行时机:当前宏任务中的JavaScript快执行完成时,也就是JavaScript引擎准备退出全局执行上下文并清空调用栈的时候,如果有微任务队列则顺序执行队列中的微任务。

image.png

image.png

  1. 为什么需要微任务? 为了权衡效率和实时性,又出现了微任务。消息队列的任务称为宏任务,每个宏任务中都包含一个微任务队列。 当前宏任务执行完成后,执行当前宏任务中的微任务,最后再执行下一个宏任务。

基于微任务的技术有MuatationObserverPromise及以Promise为基础开发出来的其他技术。

总结

本文介绍了浏览器中的页面循环机制,相信对于理解JavaScript的异步编程有了很大帮助。