再学浏览器EventLoop

956 阅读4分钟

一、概览

event loop(事件循环)是一个执行模型浏览器和NodeJS基于不同的技术实现了各自的Event Loop。

1. 浏览器的Event Loop

浏览器的Event Loop是在Html5规范(参考:html.spec.whatwg.org/multipage/w… Loop模型,具体的实现流程了浏览器厂商。

2. NodeJS的Event Loop

NodeJS的Event Loop是基于libuv(跨平台异步IO库)实现的。可以参考Node的官方文档以及libuv的官方文档。libuv已经对Event Loop做出了实现(参考:github.com/libuv/libuv…

二、Node中的EventLoop

参见:juejin.cn/post/692169…

三、浏览器的EventLoop

1. 结合上图先了解几个概念

函数调用栈: 任务(代码)具体的执行是在函数调用栈中;对应上图的Stack

队列(宏队列,微队列): 用来管理任务(代码)执行顺序的。对应上图的 Task Queue 和 Microtask Queue

Background Threads: 浏览器定时器触发线程、http异步请求线程....等线程用于处理对应的事件代码;对应上图的Background Threads。setTimeout是由定时器线程执行的,对应的回调函数会被推入宏任务中,最终在函数调用栈中执行;http异步请求是由http异步请求线程执行的,对应的回调函数会被推入宏任务中,最终也是在函数调用栈中执行....

函数调用栈用来具体执行代码,队列用来控制代码的执行顺序,浏览器一些线程模块用于处理对应的事件。

2.宏队列和微队列

宏队列,宏任务,macrotask,也叫tasks。一些异步任务的回调会依次进入macro task queue,等待后续被调用,这些异步任务包括:

  • setTimeout
  • setInterval
  • requestAnimationFrame (浏览器独有)
  • I/O
  • UI rendering (浏览器独有) 微队列,微任务,microtask,也叫jobs。另一些异步任务的回调会依次进入micro task queue,等待后续被调用,这些异步任务包括:
  • Promise
  • Object.observe
  • MutationObserver

3.宏任务微任务执行总结

  1. 先执行宏任务,再执行微任务;再执行宏任务....宏任务微任务交替循环执行。
  2. 执行宏任务的过程中产生了新的宏任务就放在宏任务队列中,产生了新的微任务就放在微任务的任务队列中。
  3. 宏任务是一个一个执行的;执行完一个宏任务,会执行本轮产生的所有微任务。

4.例子

下面通过一个例子结合上面的图片及总结,详细的分析一下,浏览器的执行过程及执行顺序

setTimeout(()=>{
    console.log("5.setTimeout1");
    Promise.resolve().then(data => {// 放入微任务队列(标记5)
        console.log(6);
    });
}, 100); // 执行到这个setTimeout浏览器会将它交给setTimeout模块去执行(标记3),在100ms后执行后回调函数放入到宏任务队列(标记4)
console.log(0) //  同步任务立即输出
setTimeout(()=>{ // 同上
    console.log("4.setTimeout2");
});
new Promise((resolve, reject) => {
    console.log('1.new promise') // new Promise 是同步任务 本身会立即执行,所以这里的console会立即输出
    resolve('2.new promise resolve')
}).then(res => { // resolve任务会被 放入到 微任务队列 (标记1)
    console.log(res) 
})
Promise.resolve().then(data=>{// 放入微任务队列 (标记1)
    console.log(3); 
});

宏任务队列咱们用【】标识;微任务队列咱们用[]标识

执行最开始=>整个js代码可以看做是一个宏任务,此时宏任务队列就是【整个js代码】

  1. 执行宏任务,整个js代码,进入函数调用栈去执行
  2. 遇到setTimeout,交给setTimeout模块去管理(标记3);
  3. 遇到console.log(0),同步代码立即输出
  4. 遇到setTimeout,交给setTimeout模块去管理(标记3);0ms后setTimeout模块会将这个setTimeout的回调放入到宏任务队列(标记4);此时宏任务队列是【setTimeout回调】
  5. 遇到new Promise, console.log('1.new promise')是同步任务立即输出, resolve任务会被 放入到 微任务队列 (标记1) [Promise]
  6. 遇到Promise.resolve().then,会被 放入到 微任务队列 (标记1) [Promise, Promise]
// 此时的输出:

0
1.new promise

// 此时的任务队列
// 宏任务【setTimeout回调】  微任务  [Promise, Promise]
  1. 执行微任务:在执行完上面的宏任务后,开始执行本轮宏任务产生的微任务,也就是两个[Promise, Promise]
// 此时的输出:

2.new promise resolve
3

// 此时的任务队列
// 宏任务【setTimeout回调】  微任务  []
  1. 执行宏任务 :执行完微任务后继续执行宏任务 【setTimeout回调】
// 此时的输出:

4.setTimeout2

// 此时的任务队列
宏任务【】  微任务  []
  1. 程序等待了100ms...后;浏览器setTimeout模块会将这个setTimeout的回调放入到宏任务队列(标记4);此时宏任务队列是【setTimeout回调】
  2. 执行宏任务 【setTimeout回调】console.log("5.setTimeout1")同步任务会立即执行;遇到Promise.resolve().then,会被 放入到 微任务队列 (标记1) [Promise]
  3. 执行微任务 [Promise]
//整体执行结果
0
1.new promise
2.new promise resolve
3
4.setTimeout2
5.setTimeout1
6