浏览器的事件循环机制(event loop)

173 阅读3分钟

什么是事件循环

事件循环是浏览器的渲染主线程的工作方式。渲染主线程会开启一个无限循环不断的到消息队列中取任务,如果消息队列中有任务则依次执行,如果没有任务则休眠,这个无限循环读取任务的过程就叫做事件循环机制。

为什么要设计事件循环机制

渲染主线程负责执行html,css,js等等工作,所以它不可以阻塞,如果阻塞则后续代码无法执行,造成页面卡顿的现象。那么如果js遇到网络请求,定时器这些需要等待的情况怎么办呢?答案是浏览器会把这些需要等待的任务交给其他线程等待,当定时器到时间了,则负责管理定时器的线程会将回调函数包装成任务放到消息队列的末尾等待渲染主线程调度执行。

消息队列

过去将消息队列分为宏任务和微任务,微任务具有最高的优先级,微任务主要为Promise,其他的异步操作如定时器,用户交互,网络请求均为宏任务。当渲染主线程执行完当前任务,要去消息队列中取任务时,会优先提取微任务队列中的任务。 最新的W3C官方文档中表示,消息队列要分为微任务和其他多种任务类型,相同的任务类型必须在同一个队列中,不同的任务类型可以在同一个队列。微任务同样具有最高的优先级,其他队列的优先级由浏览器自行决定。chrome浏览器认为用户交互的优先级较高。

下面举一个简单的例子。

Promise.resolve().then(function promise1 () { 
    console.log('promise1'); 
})
setTimeout(function setTimeout1 (){ 
    console.log('setTimeout1') 
    Promise.resolve().then(function promise2 () { 
        console.log('promise2'); 
    }) 
}, 0)
setTimeout(function setTimeout2 (){ 
    console.log('setTimeout2') 
}, 0)

此段代码中渲染主线程顺序执行,当遇到Promise代码时,将then中的promise1函数放到微任务队列。然后继续执行,执行到定时器时,将定时器setTimeout1函数放到其他任务队列末尾(也可以说是宏队列),继续执行,执行到第二个定时器,同样的会将setTimeout2函数放到上一个定时器所在的任务队列末尾。代码执行完毕,主线程开始从消息队列中取任务,首先取的是微任务队列中的任务,取出promise1函数开始执行,打印promise1,微任务队列中没有任务了,开始去其他任务队列中取任务,取出setTimeout1函数执行,打印setTimeout1,然后遇到Promise,将回调函数promise2放到微任务队列末尾,此时消息队列中的情况为:微任务队列中存在promise2,其他任务队列中存在setTimeout2,主线程首先提取微任务队列中的任务执行,打印promise2,然后微任务队列中没有任务了,主线程去其他任务队列中提取setTimeout2执行,打印setTimeout2,代码执行完毕,主线程进入休眠。

所以打印顺序为promise1,setTimeout1,promise2,setTimeout2