事件轮询(Event Loop)
JavaScript的一大特点就是是单线程,所有任务都需要在主线程里排队等待执行。
而JavaScript里的任务又分为同步任务和异步任务两种,基于事件循环(Event Loop)机制执行任务。
同步任务作为首要任务会在主线程里执行,异步任务则被“发配”到由另一个线程管理的任务队列中等待处理。异步任务符合条件(比如ajax请求到数据,setTimeout延时到期)后,会在任务队列中添加可执行“事件”,等待主线程中的同步任务执行完毕到任务队列里读取当前可执行的任务,将其加入主线程中执行,以此循环。
根据HTML Standard中的描述,一个事件循环中的执行流程大致如下。
1.选择最早的任务
2.设置事件循环中当前任务为上一步中选择的任务
3.执行该任务
4.将事件循环中的当前任务重新设置为空
5.将主线程中执行的任务移除
6.执行Microtask中的任务
7.执行页面渲染步骤,更新UI
而任务队列存在可大概两种类型,一种为microtask queue(微任务队列),另一种为macrotask queue(宏任务队列),宏任务队列在node中有很多类(check,poll,timers),每一类都是一个队列 setImmediate setTimeout,有多个都会单独放一个队列
执行机制
1.主线程执行完后会先到micro-task队列中读取可执行任务
2.主线程执行micro-task任务
3.主线程到macro-task任务队列中读取可执行任务
4.主线程执行macro-task任务
5.转到Step 1
setImmediate(() => {
console.log('setImmediate1');
process.nextTick(() => {
console.log('nextTick1')
});
setTimeout(() => {
console.log('setTimeout1')
}, 0)
});
setTimeout(() => {
console.log('setTimeout2');
Promise.resolve().then(()=>{
console.log("promise")
});
setImmediate(() => {
console.log('setImmediate2')
})
}, 0);
结果
setTimeout2
promise
setImmediate1
nextTick1
setImmediate2
setTimeout1
setTimeout 有最小4ms延迟,结果并不唯一,看代码复杂程度解析花费的时间
setImmediate(()=>{
console.log('setImmediate1')
});
setTimeout(()=>{
console.log('setTimeout2')
},0);
多运行几次就有两种结果
Macrotasks包括: script(整体代码)、setTimeout, setInterval, setImmediate, I/O, UI Rendering;
Microtasks包括: process.nextTick, Promise, Object.observe, MutationObserver。
async/await仅仅影响的是函数内的执行,而不会影响到函数体外的执行顺序。也就是说并不会阻塞后续程序的执行,await async()
相当于一个Promise,相当于前方Promise的then之后执行的函数 ,但是async/await有时候会推迟两轮microtask,在第三轮microtask执行,主要原因是浏览器对于此方法的一个解析,由于为了解析一个await,要额外创建两个promise,因此消耗很大。后来V8为了降低损耗,所以剔除了一个Promise,并且减少了2轮microtask,所以现在最新版本的应该是“零成本”的一个异步
process.nextTick
优先级高于Promise.then
大概来说每当一个轮询执行完,从Macrotask获取时间都会先检查是否有微任务,有的话就执行