Try to understand Event Loop

194 阅读4分钟

一、了解浏览器模型

1、用户界面(User Interface):包括地址栏、前进/后退按钮、书签菜单等

2、浏览器引擎(Browser engine):在用户界面和呈现引擎之间传送指令

3、呈现引擎(Rendering engine):又称渲染引擎,也被称为浏览器内核,在线程方面又称为UI线程

4、数据存储(Data Persistence):这是持久层。浏览器需要在硬盘上保存各种数据,例如 Cookie

5、网络(Networking):用于网络调用,比如 HTTP 请求

6、JavaScript解释器(JavaScript Interpreter):用于解析和执行 JavaScript 代码

7、用户界面后端(UI Backend):用于绘制基本的窗口小部件,UI线程和JS共用一个线程

在图中,1、2、3、4是属于进程,5、6、7是3进程下的线程,6和7属于同一个线程。

二、了解线程

浏览器内核的是一个多线程处理,它主要包含如下几个线程:

1、GUI渲染线程: 渲染页面的html元素;

2、JavaScript引擎线程: 页面的交互和dom渲染;

3、定时触发器线程:一定时间后,来触发对应的线程

4、事件触发线程:当一个事件触发该线程的时候,就会把它放到js的事件队列中等待执行。常用于异步操作。

5、异步http线程: 在XMLHttpRequest在连接后是通过浏览器新开一个线程请求, 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件放到 JavaScript引擎的处理队列中等待处理。

三、了解任务队列

1、所有同步任务都在主线程上执行,形成一个执行栈;

2、主线程之外,还存在一个任务队列。只要异步任务有了运行结果,就在任务队列之中放置一个事件;

3、一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行;

4、主线程不断重复上面的第三步。

四、来到Event Loop

主线程从任务队列中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)

Event-Loop

五、需要知道的一些宏任务和微任务

常见的宏任务和微任务:

1、宏任务setTimeoutsetImmediate(ie下支持)、setIntervalMessageChannelI/Oscript代码块等;

2、微任务Promise.thenMutationObservernextTickcallbackprocess.nextTickObject.observe等;

3、注意:事件队列在同步队列执行完后,首先会执行nextTick,等nextTick执行完成后,然后会先执行micro task, 等micro task队列空了之后,才会去执行macro task,如果中间添加了micro task加入了micro task队列,会继续去执行micro task队列,然后再回到macro task队列。js引擎存在monitoring process进程, 会不停的监听task queue

六、例子说明

<script>
async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}
async function async2() {
  console.log('async2');
}
console.log('script start');
setTimeout(function() {
    console.log('setTimeout');
}, 0);
async1();
new Promise(function(resolve) {
    console.log('promise1');
    resolve();
  }).then(function() {
    console.log('promise2');
  });
console.log('script end');
</script>

解析

1、整个代码块作为一个宏任务,进入主线程;

2、看到函数申明但没有执行,遇到函数console.log执行,输出script start

3、遇到setTimeout(),把它的回调函数放入宏任务(setTimeout);

4、遇到执行async1(), 进入async的执行上下文之后,遇到console.log输出async1 start

5、然后遇到await async2(),由于()的优先级高,所有先执行async2(),进入async2()的执行上下文;

6、看到console.log输出async2,之后没有返回值,结束函数,返回undefined,返回async1的执行上下文的await undefined,由于async函数使用await后得语句会被放入一个回调函数中,所以把下面的放入微任务中;

7、结束async1,返回全局上下文,遇到Promise构造函数,里面的函数立马执行, 输出promise1, 之后的回调函数进入微任务;

8、执行完Promise(),遇到console.log,输出script end,这里一个宏任务代码块执行完毕;

9、在主线程执行的过程中,事件触发线程会一直监听异步事件,当异步事件处理完成后,把它的回调函数放入事件队列,等待执行;

10、主线程现在空闲下来后,执行事件队列中的微任务,然后继续向下执行,遇到new Promise()后面的回调函数,执行代码,输出promise2(这里2个微任务的优先级,promise高于async);

11、看到async1await后面的回调函数,执行代码,输出async1 end

12、此时微任务中的队列为空,开始执行队列中的宏任务,进入一个新的代码块。遇到console.log,输出setTimeout

13、执行完成,最后结果为

script start => async1 start => async2 => promise1 => script end => promise2 => async1 end => setTimeout

七、结束

链接参考:

event loop一篇文章足矣