JS事件循环
什么是事件循环
简单地概括,事件循环就是JS调度同步、异步任务的策略
同步任务意思就是代码是同步执行的,异步任务则是代码是异步执行的,因为JS是单线程的,所以如果JS全部采用同步任务的方式,那么遇到setTimeout这种语句,得等时间走完才能执行下面的内容
事件循环的方式就是,直接执行同步任务,将异步任务交付给其它线程,当异步任务执行完后往事件队列里面塞一个回调函数,当执行栈为空时,主线程才会去读取事件队列,看看有没有任务(异步任务执行完的回调)要执行,每次取一个来执行,重复直到事件队列为空
知道这一点,以下代码就很容易得出,先输出同步任务,再输出异步任务的结论
1 | setTimeout(() => { |
宏任务与微任务
首先来说明一下why,为什么有宏任务和微任务之分,有的时候,一些异步操作并不想要经历整个事件循环,所以有了微任务,与之相对的就是宏任务
宏任务(Marco)和微任务(Micro)的执行顺序是,第一次事件循环,整段代码作为宏任务进入主线程执行,同步代码被直接推到执行栈执行,遇到异步代码就挂起交由其他线程执行(执行完会往事件队列塞回调),同步代码执行完,读取微任务队列,若有则执行所有微任务,微任务清空,页面渲染,从事件队列面里取一个宏任务塞入执行栈执行,重复上述过程
1 | # 宏任务 |
宏任务和微任务的划分如下
任务 | Chrome | Node | 分类 |
---|---|---|---|
I/O | √ | √ | Marco |
requestAnimationFrame | √ | × | Marco |
setTimeout | √ | √ | Marco |
setInterval | √ | √ | Marco |
setImmediate | × | √ | Marco |
process.nextTick | × | √ | Micro |
MutationObserver | √ | × | Micro |
Promise | √ | √ | Micro |
ok,先来看一个简单的例子
1 | console.log('script start'); |
首先执行整段代码,将script start和script end加入执行栈,promise1和promise2加入微任务队列,setTimeout加入事件队列,执行,输出script start和script end,然后清理微任务队列,输出promise1, promise2,最后输出setTImeout
再看一个稍微复杂的例子
1 | console.log('script start'); |
输出的顺序是
1 | script start |
promise本身的内容是立即执行的,只有then中的部分才属于micro task
再来看一个终极版本
1 | console.log("script start") |
首先会输出script start和script end,然后点击window,将click1加入主执行栈,clicked promise1,第三个Promise加入微任务队列,第一个和第二个setTimeout加入事件队列
第一次执行输出click1,并清理微任务队列,输出clicked promise1,clicked3,继续执行微任务输出clicked promise3,将第三个setTimeout加入事件队列
执行宏任务clicked timeout1,无微任务
执行第二个setTimeout,输出clicked2,将clicked promise2加入微任务,将clicked timeout2加入事件队列,清理微任务,输出clicked promise2
执行宏任务clicked timeout2,无微任务 (顺序执行)
执行宏任务clicked timeout3,无微任务
总体顺序如下
1 | script start |
交换第二部分和第三部分的代码,就会交换clicked timeout2和clicked timeout3的执行顺序