一、介绍
JavaScript事件循环机制是JavaScript中处理异步操作的一种机制。它基于事件队列和事件循环的概念,用于管理和执行异步任务。
当JavaScript代码执行时,同步任务会立即执行,而异步任务会被放入事件队列中。事件队列是一个先进先出的数据结构(队列),用于存储待执行的异步任务。
事件循环是一个持续运行的过程,它会不断地从事件队列中取出任务并执行。具体的执行过程如下:
- 执行同步任务:JavaScript引擎会按照代码的顺序执行同步任务。
- 执行微任务:在同步任务执行完毕后,JavaScript引擎会检查微任务队列。微任务是一类特殊的异步任务,它们的优先级比普通异步任务更高。如果微任务队列不为空,引擎会依次执行微任务,直到微任务队列为空。
- 执行宏任务:如果微任务队列为空,引擎会检查宏任务队列。宏任务包括定时器回调、事件回调等。引擎会选择最早进入队列的宏任务执行其回调函数。
- 更新渲染:在执行完宏任务后,如果浏览器需要进行页面渲染,会进行页面重绘和重排。
- 重复上述步骤:重复执行上述步骤,直到事件队列和微任务队列都为空。
二、同步任务与异步任务
JavaScript是一门单线程语言,但是单线程并不意味着阻塞。实现单线程非阻塞的方式就是事件循环机制。在JavaScript中,所有的事件都可以分为同步任务和异步任务。
- 同步任务:立即执行的任务。同步任务一般会直接进入到主线程执行。
- 异步任务:异步执行的任务,比如ajax请求、setTimeout定时器等。
任务进入执行栈,会先判断当前任务是同步任务还是异步任务,如果是同步任务则会进入到主线程,立即执行;异步任务会先放到Event Table,注册回调函数到Event Queue。等待所有的同步任务执行完后,主线程会去Event Queue中读取异步任务到主线程中执行,这个过程的不断重复就是事件循环。
三、微任务和宏任务
微任务在DOM渲染前触发,宏任务在DOM渲染后触发
**宏任务:**由浏览器规定的
- setTimeout
- setInterval
- Ajax
- DOM事件
**微任务:**由ES6语法规定的
- Promise
- async
- await
微任务和宏任务的执行机制:
执行一个宏任务,如果遇到微任务就将它放到微任务的事件队列中;当前宏任务执行完成后,会查看微任务的事件队列,然后将里面的所有微任务依次执行完。
例题:
console.log(1)①
setTimeout(()=>{
console.log(2)②
},0)
new Promise((resolve,reject)=>{
console.log(3)③
resolve()
}).then(()=>{
console.log(4)④
})
console.log(5)⑤
执行过程为:
(1)①是同步任务,直接打印1,
(2)②为宏任务,
(3)③是同步任务,直接打印3,
(4)④是微任务,放入微任务队列,后面再执行
(5)⑤是同步任务,直接打印5.
(6)本轮宏任务执行完毕,现在去微任务列表查看是否有微任务,④是微任务,执行打印4
(7)一次宏任务执行完,再去执行新的宏任务,即定时器宏任务,打印2
所以最终打印的结果是1–>3–>5–>4–>2
四、async与await
async用于声明异步函数,await用于等待异步任务执行
async函数返回一个promise对象,await命令后面是一个Promise对象,返回该对象的结果。如果不是Promise对象,直接返回对应的值,不管await后面跟着什么,都会阻塞后面的代码!!
await的执行机制:await fn():会立即执行fn(),但是会阻塞fn()后面的代码(加入微任务队列)
例题:
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')⑥
})
async1()⑦
new Promise(function (resolve) {
console.log('promise1')⑧
resolve()
}).then(function () {
console.log('promise2')⑨
})
console.log('script end')⑩
第一轮循环:先执行⑤,打印出:script start,setTimeout为宏任务,放入宏任务队列;执行⑦,进入async1(),先打印:async1 start,遇到await会阻塞后面的语句执行,所以将③放入微任务队列,立即执行②,打印出async2;Promise.then()为微任务,⑨放入微任务队列,Promise立即执行打印:promise1,接着向下执行打印script end
宏任务:setTimeout
微任务:③、⑨
所以最终打印的结果是:script start–>async1 start–>async2–>promise1–>script end–>async1 end–>promise2–>settimeout