javascript中的事件循环
一、什么是事件循环
javascript是一门单线程语言,单线程可以用事件循环的方法实现不阻塞。
javascript的任务可以分为两种:
- 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行。
- 异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等。
同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,回去任务队列读取对应的任务,推入主线程执行,上述过程的不断重复就是事件循环。
二、宏任务和微任务
javascript任务还可以分为宏任务和微任务。
1、微任务
一个需要异步执行的函数,执行时机是在主函数执行结束之后,当前宏任务结束之前。
常见的微任务有:
- Promise.then
- MutationObserver
- Object.observe(已废弃:Proxy对象代替)
- process.nextTick(Node.js)
2、宏任务
宏任务的事件粒度比较大,执行事件间隔是不能精确控制的,对一些高时效性的需求就不太符合。
常见的宏任务有:
- script(可以理解微外层同步代码)
- setTimeout/setInterval
- UI rendering/UI 事件
- postMessage、MessagChannel
- setImmediate、I/O(Node.js)
宏任务和微任务的执行流程:
- 执行一个宏任务,如果遇到微任务,将微任务放到微任务的事件队列中
- 在当前宏任务执行完毕后,查看微任务的事件队列,将里面的微任务依次执行完
console.log(1)
setTimeout(()=>{
console.log(2)
}, 0)
new Promise((resolve, reject)=>{
console.log('new Promise')
resolve()
}).then(()=>{
console.log('then')
})
console.log(3)
首先是console.log(1),打印1;遇到setTimeout,这是新的宏任务,在后面执行;遇到new Promise,打印"new Promise";.then是微任务,放入微任务列表,后面执行;遇到console.log(3),打印3;本轮宏任务执行完毕,查看微任务列表,里面有.then,执行.then的回调,打印"then";微任务也执行完毕,去执行另一个宏任务–setTimeout,打印2。
三、async和await
async是异步的意思,用来声明异步的一个方法,await是用来等待异步方法执行。
1、async
async函数返回一个promise对象。
function f() {
return Promise.resolve('TEST');
}
// asyncF is equivalent to f! 这两种等效
async function asyncF() {
return 'TEST';
}
2、await
一般来说,await后面是一个Promise对象,返回该对象的结果,如果不是Promise对象,就直接返回对应的值。
async function f(){
// 等同于
// return 123
return await 123
}
f().then(v => console.log(v)) // 123
不论await后面的是什么,await都会阻塞后面的代码。
async function fn1 (){
console.log(1)
await fn2()
console.log(2) // 阻塞
}
async function fn2 (){
console.log('fn2')
}
fn1()
console.log(3)
// 输出的顺序是 1 fn2 3 2
四、流程分析
async function async1() {
console.log('async1 start') // 4
await async2() // 5
console.log('async1 end') // 7
}
async function async2() {
console.log('async2') // 6
}
console.log('script start') // 1
setTimeout(function () {
console.log('settimeout') // 2
})
async1() // 3
new Promise(function (resolve) {
console.log('promise1') // 8
resolve()
}).then(function () {
console.log('promise2') // 9
})
console.log('script end') // 10
输出结果如下:
script start;async1 start;async2;promise1;script end;async1 end;promise2;settimeout
程序首先遇到 1 号,打印 script start ;然后遇到 2 号,是宏任务,先处理完当前宏任务和微任务再另外处理这个 2 号宏任务;遇到 3 号,执行 4 号,打印 async1 start ,遇到 5 号,执行 6 号,打印 async2 ,阻塞 7 号,将 7 号添加到微任务队列;遇到 8 号,执行,打印 promise1 ,遇到 9 号,将 9 号添加到微任务队列;遇到 10 号,执行,打印 script end。
至此,宏任务结束,依次执行微任务列表。
执行 7 号,打印 async1 end ,执行 9 号,打印 promise2 。
至此,微任务列表执行完毕,执行下一个宏任务。
执行 2 号,打印 settimeout。
全部执行完毕。