事件机制
事件循环 eventloop
当函数执行栈为空,js 引擎从任务队列获取任务、执行任务、栈为空,再获取任务,这几个状态之间转换的无限循环
任务 tasks
任务队列会有多个,为帮助理解,将其称为宏任务
即普通 js 任务,一段普通的 js 代码,包括 setTimeout、setInterval、mousemove 等事件所产生的回调,它们都会在任务队列(task queue)上被调度。多个任务组成一个队列,即所谓的“宏任务队列”(V8 术语)
setTimeout、setInterval 产生的任务会被安排在队列末尾,在下一次循环开始时执行
微任务 microtasks
微任务队列只有一个
queueMicrotask():手动添加微任务进入队列
微任务会在执行完当前普通任务队列时候执行。即当执行栈为空时,如果在微任务执行期间手动加入更多微任务,则微任务会持续处理,直到队列为空
注意:不应该过多的使用微任务
微任务通常由 promise 创建,then、catch、finally 的回调函数会成为微任务,但新加入的 queueMicrotask() 增加了一种标准的方式
何时使用微任务
确保任务顺序的一致性,即使当结果或数据是同步可用的,也要同时减少操作中用户可感知到的延迟带来的风险
可以使用 queueMicrotask() 方法将任务变成微任务,以使其在队列末尾执行。
示例 1
if (typeof window !== 'undefined') {
var a = document.getElementById('div');
a.addEventListener('load', () => console.log('loaded data'));
a.getData = function () {
queueMicrotask(() => {
this.dispatchEvent(new Event('load'));
});
};
console.log('fetching data');
a.getData();
console.log('data fetched');
}
示例 2
function log(str) {
console.log(str);
}
log('before');
queueMicrotask(() => {
log('microtask');
});
log('after');
示例 3
let callback = () => log('regular timeout callback has run');
let urgentCallback = () => log('_oh noes! an urgent callback has run!');
let doWork = () => {
let result = 1;
// 此函数将在 main exiting 后执行,因为此时执行栈才为空
queueMicrotask(urgentCallback);
for (let i = 2; i <= 10; i++) {
result -= i;
}
return result;
};
log('main start');
setTimeout(callback, 0);
log(`10! equals ${doWork()}`);
log('main exiting');