vlambda博客
学习文章列表

连续两场面试问event loop

Event loopjs是单线程,然后这个是解决任务在同一个时间执行会产生堵塞的情况。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);
// 正确答案1475236

被问了为什么要有宏任务和微任务?

网上找了一些解答:


大部分时候我们是不需要使用微任务的,但在某些情况下,微任务特别有用。比如,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:

  1. 保证一致的执行顺序。当我们想让某几个任务异步执行,又想让彼此之间的顺序不乱,就可以使用microtask,而不用setTimeout。

  2. 保证一致的执行环境。假设宏任务有可能改变数据,我们就可以把一些数据分析放到微任务中,确保使用的数据未被下一次宏任务修改。


故事性理解:

就像你去银行办理任务,这时候叫做宏任务,每个人都在宏任务队列中排队,等待银行叫好处理。但是只有这样一个队列是不能满足需求。

比如有一个大爷,刚刚办理了存款,然后想想存款的利息太低了,准备拿出一半的钱去买理财,那这时候,总不能让大爷去重新排队办理理财的业务把。

所以这时候,就出现微任务了,把大爷放到微任务队列中,然后在理财窗口当前一轮业务办理完后,优先给大爷办理业务。再给大爷办理完任务后,再继续给其他人办理任务。


微任务和宏任务拥有不同的职责和功能,来满足我们的需求。