问题描述
我正在学习执行堆栈,任务队列和事件循环机制。
我不断单击按钮,直到可用主线程(在完成函数a()之前)如下。
我以为click(UI)事件和setTimeout使用相同的队列,称为Macrotask或Task Queue,因此当我单击1s和3s之间的按钮时,我认为task2的日志打印在task1和task2之间。但是结果却并非如此。始终先打印Task2(单击事件),并在单击事件之后打印setTimeout事件(task1,task3)。
所以我想知道点击事件是否使用与setTimeout不同的队列机制,或者点击事件的优先级高于setTimeout。
预先感谢您的帮助
操作
- 单击按钮(任务2)
- -------- 1000毫秒setTimeout task1 --------
- 单击按钮(任务2)
- -------- 3000毫秒setTimeout task3 --------
- 单击按钮(任务2)
- -------- 6000ms主线程现已可用--------
我的期望日志订单
fn a done
task2 (click) done
task1 (setTimeout 1000ms) done
task2 (click) done
task3 (setTimeout 3000ms) done
task2 (click) done
结果日志顺序
fn a done
task2 (click) done
task2 (click) done
task2 (click) done
task1 (setTimeout 1000ms) done
task3 (setTimeout 3000ms) done
代码
const btn = document.querySelector('button');
btn.addEventListener('click',function task2() {
console.log('task2 (click) done');
});
function a() {
setTimeout(function task1() {
console.log('task1 (setTimeout 1000ms) done');
},1000);
setTimeout(function task3() {
console.log('task3 (setTimeout 3000ms) done');
},3000);
// hold main thread for 6000ms(*1)
const startTime = new Date();
while (new Date() - startTime < 6000);
console.log('fn a done');
}
a();
<button>button</button>
<script src="main.js"></script>
解决方法
让我们看看您使用setTimeout和while循环正在做什么。
如果运行代码片段,您将看到1秒和3秒超时的时间戳基本上是相同的。这是因为while循环将主线程阻塞了6秒钟,直到可以运行setTimeout中的函数为止。
仅在主线程空闲时才在1秒或3秒后不完全执行回调。 setTimeout保证回调函数至少在x毫秒后执行,这对于任务1 >> 1秒,对于任务3> = 3秒。
kubectl delete pod coredns-f9fd979d6-sw2qp --namespace=kube-system
,
我认为click(UI)事件和setTimeout使用相同的队列
他们没有。
UI事件使用user interaction(UI) task source,它在大多数浏览器中都有自己的task-queue,setTimeout
使用timer task-source,而在大多数浏览器中也都有自己的task-queue浏览器。
尽管规范没有要求,但UI任务源在几乎所有浏览器中的优先级是所有任务源中的最高之一,而计时器的优先级是最低的。
此优先级的工作方式是,在event-loop's processing model的第一步中,用户代理(UA)必须从其事件队列中选择一个将执行的 task 事件
注意:通俗地称为“宏任务” 是任何不是microtask的任务。
The microtask queue is not a task queue,虽然在事件循环处理的第一步中可以选择微任务作为主要任务,但微任务队列不能被优先排序,因为必须清空在每个microtask-checkpoints上同步 ,这在每次事件循环迭代中都可能发生多次,尤其是after the chosen task gets executed。
因此,在这里,while
循环完成后,阻塞了事件循环,并开始了下一次迭代,UA必须选择从哪个任务队列中选择下一个任务。
它将在其 UI任务队列中看到有新事件在等待,并执行它们,因为它们具有更高的优先级。
然后,当它们全部执行完毕后,它将选择 timers队列,并按照其计划时间执行它们。
还请注意they have饥饿系统,它会阻止高优先级的任务队列长时间阻止其他任务队列。
最后,我要提一个建议,让我们的Web开发人员直接处理所有这些优先事项:main thread scheduling。
使用此实验功能,我们可以将代码段重写为
if( !("scheduler" in window) ) {
console.error("Your browser doesn't support the postTask API");
console.error("Try enabling the Experimental Web Platform features in chrome://flags");
}
else {
scheduler.postTask(() => {
console.log('task1 (background) done');
},{ priority: "background" } );
scheduler.postTask(() => {
console.log('task2 (background) done');
},{ priority: "background" } );
// hold main thread for 6000ms(*1)
const startTime = new Date();
while (new Date() - startTime < 2000);
scheduler.postTask(() => {
console.log('task3 (user-blocking) done');
},{ priority: "user-blocking" } );
console.log('synchronous done');
}
并看到最终任务首先执行。