你还不会EventLoop?

134 阅读4分钟

前言

作为一个2022年的前端工程师(切图仔),需要面对对象(浏览器和JavaScript)进行开发,那在和这两大高手的交流中我们需要知道的知识是哪些呢,下面罗列了一些应该掌握的知识:

  • JavaScript:数据类型及其结构
  • JavaScript:堆栈及闭包作用域
  • JavaScript:原型及原型链
  • JavaScript:Ajax及Fecth数据请求
  • 浏览器:浏览器的渲染原理
  • 浏览器:浏览器队列机制
  • ... 以上简单列举自己掌握的知识,后续掌握更多进行更新。今天就来回顾自己掌握的事件循环机制EventLoop

浏览器介绍

在接触浏览器之前,了解了进程和线程。进程是资源分配的最小单元,线程是资源调度和执行的最小单元。结合浏览器来看,在浏览器开辟一个Tab页就是开辟了一个进程,每个进程中互不影响,一个进程包含一个或者多个线程。

浏览器的多线程

  1. GUI主渲染线程:将HTML渲染为试图的主线程;
  2. JavaScript引擎线程:在浏览器中执行JavaScript代码的线程;
  3. 事件触发监听线程:监听dom元素事件触发的线程;
  4. 定时器监听线程:监听定时器到达的线程;
  5. HTTP网络请求线程:监听HTTP数据请求的线程;
  6. WebWorker;
  7. ...

同步异步编程

浏览器被设计成多线程,可以同时处理多件事情,属于异步编程,而在浏览器中执行的JavaScript代码则只能通过JavaScript引擎线程执行(属于单线程),属于同步编程(大多数JavaScript代码是同步执行,但是也存在一些代码是被异步调度执行的,这里的异步并不是同时做,而是根据一定的规则进行执行,当然就是本文的重点啦:EventLoop)。

JavaScript的同步和异步

  1. 同步:当JavaScript代码在JavaScript引擎进行执行的时候,因为属于单线程,只能自上而下依次执行,因此属于同步执行(当代码出现死循环的时候,浏览器就会卡死);
  2. 异步:JavaScript中的异步是是基于EventLoop&浏览器的多线程机制实现出监听、排队的机制;基于这个机制,区分了不同优先级的任务(宏任务和微任务);
    • 异步微任务:优先级比较高
      • Promise.then
      • await
      • requestAnimationFrame
      • queueMicroTask
      • InsectionObserver / MutationObserver / PerformanceObserver ...监听者
      • process.nextTick (node)
      • ...
    • 异步宏任务:优先级比较低
      • setTimeout / setInterval
      • 事件绑定
      • Ajax / Fetch 网络请求
      • setImmediate (node)
      • ...

EventLoop&EventQueue

基于一道题解释:

setTimeout(() => {
    console.log(1);
}, 20);
console.log(2);
setTimeout(() => {
    console.log(3);
}, 10);
console.log(4);
for (let i = 0; i < 90000000; i++) { 
    // 大约花费70ms
}
console.log(5);
setTimeout(() => {
    console.log(6);
}, 8);
console.log(7);
setTimeout(() => {
    console.log(8);
}, 15);
console.log(9);

1. 浏览器会维护两个队列:任务监听队列、事件队列;
2. 代码自上而下执行,遇到setTimeout,将其放置到任务监听队列中,监听到达执行的条件,暂时将其标记为macroTask@1(20);
3. 打印 24. 将macroTask@3(10)入任务监听队列;
5. 打印46. 循环代码,在花费10ms时,任务监听队列中的macroTask@3(10)到达可执行条件,将其入事件队列;在花费20ms时,任务监听队列中的macroTask@1(20)到达可执行条件,将其入事件队列;
7. 打印 58. 将macroTask@6(80)入任务监听队列;
9. 打印 710.  将macroTask@8(15)入任务监听队列;
11. 打印 912. 此时同步代码执行完毕,主线程空闲下来,浏览器会在反应5-7ms后去Event Queue中查找可执行的任务(区分微任务和宏任务,并且两个任务中的优先级按照入队顺序继续比较),此时有macroTask@3(10)和macroTask@1(20)可执行且macroTask@3(10)优先级高,将其取出入栈执行打印 313. 此时主线程又空闲下来,接着去查找,取出macroTask@1(20)入栈执行,打印 114. 时间过去8ms,这个时候macroTask@6(8)到达执行条件,入事件队列(此时主线程时空闲状态);
15. 主线程通过Event Loop找出macroTask@6(8)执行,打印 616. 时间又过去7ms,这个时候macroTask@8(15)到达执行条件,入事件队列(此时主线程时空闲状态);
15. 主线程通过Event Loop找出macroTask@8(15)执行,打印 816. 最终代码执行完,也没有存在监听的任务输出的结果是:2 4 5 7 9 3 1 6 8。


上述12-13Event Loop的原理。