定义
在计算机科学中,事件循环是一种编程构造或设计模式,用于等待和调度程序中的事件或消息。事件循环的工作原理是向某个内部或外部“事件提供程序”(通常会阻止请求,直到事件到达),然后调用相关的事件处理程序(“调度事件”)。事件循环有时也称为消息调度程序、消息循环、消息泵或运行循环 (维基百科对Event loop的定义)
JavaScript 有一个基于事件循环的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。这个模型与其他语言中的模型截然不同,比如 C 和 Java (MDN对Javascript Event loop的介绍)
前置知识
- JS引擎
单线程
执行机制 宏任务
&微任务
进入正题
JS引擎执行任务是基于EventLoop
的,大致流程为:等待任务->依次执行任务->等待任务-> ...往复循环,因此称为loop,如下图:
图片来源: javascript.info/event-loop
任务队列遵循“先进先出”的原则,如上图所示,JS引擎依次执行script
,然后是mousemove
、setTimeout
。执行完毕后,进入下一个循环。
需要注意的是:DOM更新只会发生在一个任务结束后,无论该任务有多耗时。
图片来源: javascript.info/event-loop
使用场景
- 大任务分解,提升用户体验
前端执行比较耗时的任务,比如请求到地图数据后计算组装等。在任务执行期间,JS引擎无法响应用户操作,页面会陷入暂时“卡死”状态。
let i = 0;
let start = Date.now();
function count() {
// do a heavy job
for (let j = 0; j < 1e9; j++) {
i++;
}
alert("Done in " + (Date.now() - start) + 'ms');
}
count();
通过将大任务拆成若干个小任务,在小任务执行间隙,JS引擎可以响应用户操作,DOM更新等,带来更好的体验,并且不会额外消耗太多执行时间。
let i = 0;
let start = Date.now();
function count() {
// move the scheduling to the beginning
if (i < 1e9 - 1e6) {
setTimeout(count); // schedule the new call
}
do {
i++;
} while (i % 1e6 != 0);
if (i == 1e9) {
alert("Done in " + (Date.now() - start) + 'ms');
}
}
count();
- 进度条展示
DOM更新发生在多个任务之间,所以正常来说在一个任务内多次改变一个值,用户看不到其变化过程,展现在用户面前的只有该值的初始状态和最终状态。
<div id="progress"></div>
<script>
function count() {
for (let i = 0; i < 1e6; i++) {
i++;
progress.innerHTML = i;
}
}
count();
</script>
如果我们想要展现其变化过程,如计数器、进度条等,可以使用setTimeout
来将大任务拆成多个小任务,DOM会在小任务之间更新,因此用户便可以看到DOM的变化过程了。
<div id="progress"></div>
<script>
let i = 0;
function count() {
// do a piece of the heavy job (*)
do {
i++;
progress.innerHTML = i;
} while (i % 1e3 != 0);
if (i < 1e7) {
setTimeout(count);
}
}
count();
</script>
考察题目
请写出以下代码执行后,打印的顺序
题目一
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('settimeout')
})
async1()
new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('promise2')
})
console.log('script end')
题目二
setTimeout(() => {
console.log('setTimeout start');
new Promise((resolve) => {
console.log('promise1 start');
resolve();
}).then(() => {
console.log('promise1 end');
})
console.log('setTimeout end');
}, 0);
function promise2() {
return new Promise((resolve) => {
console.log('promise2');
resolve();
})
}
async function async1() {
console.log('async1 start');
await promise2();
console.log('async1 end');
}
async1();
console.log('script end');
总结与思考
- 理解EventLoop有助于开发者排查问题、写出更优雅的代码,带来更好的体验
- 可以将
Javascript
与Node.js
的EventLoop
进行对比,分析其异同点 - 分析某个知识点时,可以顺着主线,抓住重点,参考大量文章材料后,可以先画一个思维导图再下笔
参考文章
developer.mozilla.org/en-US/docs/…