1. 前言
EventLoop也就是常说的事件循环机制。不管是浏览器还是nodejs,在代码执行过程中都会基于这套事件循环机制来实现异步编程。不同的是浏览器中的EventLoop是根据HTML定义的规范来实现的,而nodejs中的EventLoop则是基于libua来实现的。
本篇文章主要讲的是浏览器中的事件循环机制。在此之前,先来了解一下在浏览器中的js运行机制
2. JS的运行机制
浏览器是多线程的,打开一个页面,浏览器会分配很多线程,同时处理一些事情
- GUI渲染线程:自上而下渲染页面
- JS引擎(渲染)线程:JS单线程是因为,浏览器只会开辟这一个线程,用来执行JS代码
- HTTP网络请求线程:加载资源文件或是一些数据
- ...
2.1 为什么是JS是单线程的?
JS作为客户端脚本语言,主要的用途是实现用户交互以及操作DOM,所以JS是单线程(同一时间只能做一件事情)。这避免了多个线程同一时间操作同一个DOM的矛盾情况
2.2 JS为什么需要异步?
因为浏览器在执行JS代码的时候是单线程的,整块JS自上而下依次执行。如果中间有一代码块的解析执行时间很长,那么下面的代码就被阻塞,无法执行,导致了很差的用户体验。JS需要实现异步编程,把需要用户等待的代码异步执行,从而减少用户等待的时间,提高用户体验。
JS中的异步编程则是基于EventLoop来实现
3. 同步任务和异步任务
在JS代码自上而下执行的过程中,会把所有代码分为两部分:同步任务和异步任务。
同步任务会直接进到主线程的执行栈中,按照顺序等待主线程依次执行。
遇到异步任务后,会把异步任务分为两类:
- 宏任务(macro-task)
- 整体代码script
- setTimeout
- setInterval
- ...
- 微任务(mincro-task)
- promise.then
- async/await 「generator」
- requestAnimationFrame
- ...
4. Event Loop事件循环
上图Event Loop内容可总结为:
- 整体的script作为第一个宏任务开始执行的时候,会把所有代码分为两部分:同步任务和异步任务
- 同步任务会直接进到主线程的执行栈中依次执行
- 在遇到异步任务时,会把这些异步任务存放在任务队列中,根据不同的任务,分为宏任务队列和微任务队列
- 当主线程执行栈中的任务全部执行完成,执行栈空闲的时候。就会先检查微任务的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