JavaScript是一门单线程的语言,任务需要按照执行顺序来完成,下一个任务需等待上一个任务结束后才开始执行,这就是同步任务。 考虑到网页开发中,如果页面与用户之间的交互也按照同步任务执行,则会带来十分糟糕的体验,试想下当你点击某个功能按钮向服务器发送一个请求,由于网络原因或服务器问题导致请求迟迟未响应,而你只能在屏幕前干等,无法继续和网页进行交互,这怎么行呢?因此,异步任务也是很必要的,异步任务指任务执行后,不需要等待结果返回,也可以继续往下执行,当异步任务结果返回后会调用预先设置的监听函数执行后续任务。
JavaScript引擎通过事件循环机制分别执行同步任务和异步任务
事件循环
- 全局script标签作为JavaScript任务执行入口起点,作为执行栈(Call Stack),执行栈中的都是同步任务,如果遇到异步任务,则先把异步任务放到任务队列(Task Queue)或微任务队列(Microtask Queue)中。
- 执行栈中的同步任务全部执行完毕,变成空栈,则先检查微任务队列中是否有任务,有则按顺序依次把微任务放到栈中执行。
- 微任务全部执行完毕,则检查任务队列是否有任务,有则按顺序依次放到栈中执行。
- 以上便完成一轮事件循环,当执行栈为空时,JavaScript引擎会不断检测微任务队列和任务队列,把队列中的任务放到执行栈中执行,循环往复。
\
异步任务可分为宏任务(macrotask)和微任务(microtask)
宏任务
- 事件回调
- setTimeOut
- setInterVal
- setImmediate(IE10/Edge)
- UI渲染
- I/O(node.js)
微任务
- promise.then.catch.finally
- mutationsObserve
- objectObserve(已废除)
- process.nextTick(node.js)
代码例子
console.log('start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
new Promise((resolve) => {
console.log('promise')
resolve()
})
.then(() => {
console.log('then1')
})
.then(() => {
console.log('then2')
})
console.log('end')
//start
//promise
//end
//then1
//then2
//setTimeout
解析:
- 首先执行所有同步任务,log输出start
- 遇到setTimeOut,放到宏任务队列第一位等待
- 遇到Promise,Promise函数在状态变更前(refulled或rejected)作为同步代码执行,log输出promise
- 调用resolve函数更改Promise状态,遇到promise.then,放到微任务队列第一位等待
- 执行最后一行同步代码,log输出end,执行栈为空
- 检查微任务队列,发现then函数,放到栈中执行,输出then1,又遇到一个then,还是放到微任务队列,栈空
- 继续检查微任务队列,执行输出then2,执行栈和微任务队列皆为空
- 检查宏任务队列,把setTimeOut放到栈中执行,log输出setTimeout
\
总结
当JavaScript引擎运行代码时,便开启第一轮事件循环,首先执行所有同步代码,其中可能产生一系列的宏任务和微任务并放到相应的队列中等待执行,当所有同步代码执行完毕,还未结束本轮事件循环,需要把本轮产生的微任务都执行完毕,如果执行微任务过程中又产生了宏任务和微任务,那么宏任务会放到宏任务队列中等待,微任务加入本轮微任务队列的末尾,等前面的微任务执行完毕就会立刻执行,只有当微任务队列为空时,才会执行宏任务队列中的第一个任务,开启下一轮的事件循环,宏任务中产生的宏任务还是放到宏任务队列,微任务放到微任务队列中等所有同步代码执行完毕后立刻执行,再执行下一个宏任务开启下一轮事件循环……
\
本文为记录自己学习前端知识的个人理解总结,不保证理解正确到位,欢迎评论指出和纠正错误,欢迎一起学习前端。