Skip to main content

浏览器的循环系统

消息队列和事件循环系统

系统的逐步完善,与解决问题的过程

  1. 单线程可以处理好确定的任务,但是无法处理新增的任务

  2. 采用事件循环监听是否有新的事件,但是无法处理其他线程的任务

  3. 引入消息队列IO 线程产生的任务添加到消息队列,渲染主线程循环读取消息队列并执行

  4. 渲染主线程会有专门的 IO 线程,用来接受其他进程的的消息

    然而,消息队列中有非常多的消息类型:

    输入事件(鼠标滚动点击等等)、微任务、文件读写、Websocket、js 定时器等等。

    还有页面相关的 JS 执行、解析 DOM、样式布局计算、css 动画。

    这些任务都是在主线程执行的。

  5. 单线程还会有问题

    1. 如何处理高优先级任务,比如 DOM 的频繁变化

      使用微任务权衡,消息队列的每个任务为一个宏任务,每个宏任务包含一个微任务队列

    2. 如何解决单任务长时间占据主线程

      Js 采用回调功能避免

setTimeout 是如何实现的

除了正常使用的消息队列,还维护了一个延迟消息队列

  • js 调用 setTimeout ,渲染进程会创建回调任务,包含倒计时时间等信息,并放入延迟消息队列。
  • 当前的任务执行完后,会查看延迟消息队列,一次执行已经到期的队列。

使用注意事项

  • 长时间的任务会阻塞 setTimeout

  • setTImeout 被嵌套调用 5 次以上后,

  • 未激活页面,最小执行间隔 1000 毫秒

    为了优化后台页面的消耗

  • 执行延迟最大时间

    浏览器以32bit存储,所以最大是 2147483647 毫秒,超过会溢出,立即执行

  • 回调 this 不符合直觉,使用 箭头函数,或者 bind

XMLHttpRequest 是如何实现的

异步回调函数

  • 把异步函数做成一个任务,添加到消息队列队尾
  • 把异步函数添加到微任务队列

XMLHttpRequest 运作过程

  1. 创建 XMLHttpRequest 对象
  2. 注册回调函数,onerrorontimeoutonreadystatechange
  3. 配置请求信息
  4. xhr.send 发送请求

微任务和宏任务

宏任务

  • 渲染事件(解析 DOM、计算布局、绘制)
  • 用户交互事件(鼠标点击、滚动页面、resize)
  • JS 脚本执行事件
  • 网络请求完成、文件读写完成事件

WHATWG 规范

  • 先从多个消息队列中选出一个最老的任务,这个任务称为oldestTask;
  • 然后循环系统记录任务开始执行的时间,并把这个oldestTask设置为当前正在执行的任务;
  • 当任务执行完成之后,删除当前正在执行的任务,并从对应的消息队列中删除掉这个oldestTask;
  • 最后统计执行完成的时长等信息。

微任务

产生微任务有两种方式

  • MutationObserver 监控 DOM 节点
  • 使用 promise

MutationObserver采用了“异步+微任务”的策略。

  • 通过异步操作解决了同步操作的性能问题
  • 通过微任务解决了实时性的问题

Promise

  1. Promise中为什么要引入微任务?
  2. Promise中是如何实现回调函数返回值穿透的?
  3. Promise出错后,是怎么通过“冒泡”传递给最后那个捕获异常的函数?

async/await