连续两场面试问event loop
Event loop:js是单线程,然后这个是解决任务在同一个时间执行会产生堵塞的情况。js中分同步任务,异步任务,先执行同步任务,再执行异步任务中的微任务,最后执行宏任务。
宏任务:setTimeout、DOM事件回调、ajax回调
微任务:Promise、MutaionObserver、process.nextTick(Node.js 环境)
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3)
});
});
new Promise((resolve, reject) => {
console.log(4)
resolve(5)
}).then((data) => {
console.log(data);
})
setTimeout(() => {
console.log(6);
})
console.log(7);
// 正确答案
1
4
7
5
2
3
6
被问了为什么要有宏任务和微任务?
网上找了一些解答:
大部分时候我们是不需要使用微任务的,但在某些情况下,微任务特别有用。比如,Mozilla的文档中给出了以下两个例子。
例1:确保一致的执行顺序
customElement.prototype.getData = url => {
if (this.cache[url]) {
this.data = this.cache[url];
this.dispatchEvent(new Event("load"));
} else {
fetch(url).then(result => result.arrayBuffer()).then(data => {
this.cache[url] = data;
this.data = data;
this.dispatchEvent(new Event("load"));
)};
}
};
在上面的代码中,当一个url的data已经被cache过,我们就直接复用cache,否则就使用promises来读取并存到cache中。问题在于,promises仅出现在if-else的else分支中,导致了两个分支一个是同步的,一个是异步的。当getData这个函数被执行两次时,执行的顺序不同。比如
element.addEventListener("load", () => console.log("Loaded data"));
console.log("Fetching data...");
element.getData();
console.log("Data fetched");
第一次没cache,执行结果是Fetching data,Data fetched,Loaded data
第二次有cache,执行结果是Fetching data,Loaded data,Data fetched
更糟糕的是,在if分支中会同步更新this.data,而在else分支中会异步更新this.data,这就导致了在一段时间内,data的数据是不一致的。
为了解决以上的问题,可以用microtask,代码如下
customElement.prototype.getData = url => {
if (this.cache[url]) {
queueMicrotask(() => {
this.data = this.cache[url];
this.dispatchEvent(new Event("load"));
});
} else {
fetch(url).then(result => result.arrayBuffer()).then(data => {
this.cache[url] = data;
this.data = data;
this.dispatchEvent(new Event("load"));
)};
}
};
这样就保证了两个分支里的任务都会等到执行microtask时执行。
例2:可以批处理
当有多条信息需要发送时,可以在执行microtask时一次性发送。
const messageQueue = [];
let sendMessage = message => {
messageQueue.push(message);
if (messageQueue.length === 1) {
queueMicrotask(() => {
const json = JSON.stringify(messageQueue);
messageQueue.length = 0;
fetch("url-of-receiver", json);
});
}
};
在上面的代码中,当sendMessage被多次调用时,每个信息都会被加入到messageQueue中,但只有第一条会创建一个microtask。等到执行microtask的时候,会把所有加入到messageQueue的信息一次性发送到某个server,然后清空messageQueue。这样就避免了重复的发送。
除了以上的两个例子,我觉得如果有以下两个需求,也可以使用microtask:
保证一致的执行顺序。当我们想让某几个任务异步执行,又想让彼此之间的顺序不乱,就可以使用microtask,而不用setTimeout。
保证一致的执行环境。假设宏任务有可能改变数据,我们就可以把一些数据分析放到微任务中,确保使用的数据未被下一次宏任务修改。
故事性理解:
就像你去银行办理任务,这时候叫做宏任务,每个人都在宏任务队列中排队,等待银行叫好处理。但是只有这样一个队列是不能满足需求。
比如有一个大爷,刚刚办理了存款,然后想想存款的利息太低了,准备拿出一半的钱去买理财,那这时候,总不能让大爷去重新排队办理理财的业务把。
所以这时候,就出现微任务了,把大爷放到微任务队列中,然后在理财窗口当前一轮业务办理完后,优先给大爷办理业务。再给大爷办理完任务后,再继续给其他人办理任务。
微任务和宏任务拥有不同的职责和功能,来满足我们的需求。