前言
由于js是单线程的语言,当我们遇到需要等待的任务(延迟回调、网络请求)时就会阻塞线程,
于是就有了异步的概念,而事件循环正是异步的实现方式,
当你真正明白了事件循环机制后就能解释日常开发中出现的诸多执行顺序的问题例如:触发更新后为何获取不到最新的DOM、Promise.then为什么比setTimeout快等等。
事件循环(旧)
在旧的事件循环只划分了两个任务队列,微任务队列和宏任务队列
中执行一个宏任务后会清空一次微任务队列再执行下一个宏任务,
因为首次执行,宏队列中会有 script(整体代码块)任务,
所以实际上就是 Js 解析完成后,在异步任务中,会先执行完所有的微任务,
所以微任务一定会比宏任务先执行
事件循环(新)
为什么说现在的事件循环不只有微任务和宏任务了呢,接下来我们来看一个有趣的现象
<button id="begin">开始</button>
<button id="interaction">添加交互任务</button>
<script>
// 为保证延迟任务和交互任务都已添加到队列,模拟线程阻塞
function delay(duration) {
var start = Date.now();
while (Date.now() - start < duration) {}
}
function addDelay() {
console.log('添加延时队列');
setTimeout(() => {
console.log('延时队列执行');
}, 0);
delay(2000);
}
function addInteraction() {
console.log('添加交互队列');
interaction.onclick = function () {
console.log('交互队列执行');
};
delay(2000);
}
begin.onclick = function () {
addDelay();
addInteraction();
console.log('===========');
};
</script>
按照我们最初说的,交互事件与延迟事件都是宏任务,
那它们应该是谁先加入队列就先执行谁才对,所以结果应该是
- 添加延时队列
- 添加交互队列
- ==========
- 延时队列执行
- 交互队列执行
然而它真正的输出结果是
- 添加延时队列
- 添加交互队列
- ==========
- 交互队列执行
- 延时队列执行
为什么交互队列会比延时队列先执行呢,根据[W3C](HTML Standard (whatwg.org))最新的解释
每个任务都有一个任务类型,
同⼀个类型的任务必须在⼀个队列,
不同类型的任务可以分属于不同的队列。
而在⽬前chrome的实现中,⾄少包含了下⾯的队列,所以才会出现以上这种情况:
- 延时队列:⽤于存放计时器到达后的回调任务,优先级「中」
- 交互队列:⽤于存放⽤户操作后产⽣的事件处理任务,优先级「⾼」
- 微队列:⽤户存放需要最快执⾏的任务,优先级「最⾼」
总结
- 过去把消息队列简单分为宏队列和微队列,这种说法⽬前已⽆法满⾜复杂的浏览器环境,取⽽代之的是⼀种更加灵活多变的处理⽅式。
- 根据 W3C 官⽅的解释,每个任务有不同的类型,同类型的任务必须在同⼀ 个队列,不同的任务可以属于不同的队列。
- 不同任务队列有不同的优先级, 在⼀次事件循环中,由浏览器⾃⾏决定取哪⼀个队列的任务。
- 但浏览器必须有⼀个微队列,微队列的任务⼀定具有最⾼的优先级,必须优先调度执⾏。