一定要弄懂Event Loop

319 阅读3分钟

1. 前言

EventLoop也就是常说的事件循环机制。不管是浏览器还是nodejs,在代码执行过程中都会基于这套事件循环机制来实现异步编程。不同的是浏览器中的EventLoop是根据HTML定义的规范来实现的,而nodejs中的EventLoop则是基于libua来实现的。

本篇文章主要讲的是浏览器中的事件循环机制。在此之前,先来了解一下在浏览器中的js运行机制

2. JS的运行机制

浏览器是多线程的,打开一个页面,浏览器会分配很多线程,同时处理一些事情

  1. GUI渲染线程:自上而下渲染页面
  2. JS引擎(渲染)线程:JS单线程是因为,浏览器只会开辟这一个线程,用来执行JS代码
  3. HTTP网络请求线程:加载资源文件或是一些数据
  4. ...

2.1 为什么是JS是单线程的?

JS作为客户端脚本语言,主要的用途是实现用户交互以及操作DOM,所以JS是单线程(同一时间只能做一件事情)。这避免了多个线程同一时间操作同一个DOM的矛盾情况

2.2 JS为什么需要异步?

因为浏览器在执行JS代码的时候是单线程的,整块JS自上而下依次执行。如果中间有一代码块的解析执行时间很长,那么下面的代码就被阻塞,无法执行,导致了很差的用户体验。JS需要实现异步编程,把需要用户等待的代码异步执行,从而减少用户等待的时间,提高用户体验。

JS中的异步编程则是基于EventLoop来实现

3. 同步任务和异步任务

在JS代码自上而下执行的过程中,会把所有代码分为两部分:同步任务和异步任务。

同步任务会直接进到主线程的执行栈中,按照顺序等待主线程依次执行。

遇到异步任务后,会把异步任务分为两类:

  1. 宏任务(macro-task)
    • 整体代码script
    • setTimeout
    • setInterval
    • ...
  2. 微任务(mincro-task)
    • promise.then
    • async/await 「generator」
    • requestAnimationFrame
    • ...

4. Event Loop事件循环

WechatIMG215.jpeg

上图Event Loop内容可总结为:

  1. 整体的script作为第一个宏任务开始执行的时候,会把所有代码分为两部分:同步任务和异步任务
  2. 同步任务会直接进到主线程的执行栈中依次执行
  3. 在遇到异步任务时,会把这些异步任务存放在任务队列中,根据不同的任务,分为宏任务队列和微任务队列
  4. 当主线程执行栈中的任务全部执行完成,执行栈空闲的时候。就会先检查微任务的Event queue,如果有微任务,全部依次执行。如果当前微任务的Event queue没有任务,就执行下一个宏任务 上述过程会不断重复,这就是Event Loop事件循环机制

最后举个简单的栗子

console.log('script start')      // 同步代码直接在主线程的执行栈中依次执行

setTimeout(function() {          // 遇到宏任务,放到宏任务队列  
  console.log('setTimeout')
}, 0)

Promise.resolve().then(function() { // 放到微任务队列,当主线程空闲时,优先执行微任务
  console.log('promise1')
}).then(function() {
  console.log('promise2')
})
console.log('script end')        // 同步代码直接在主线程的执行栈中依次执行

// 1. script start     
// 2. script end
// 3. promise1
// 4. promise2
// 5. setTimeout