“当 JS 堆栈清空时,就会处理微任务(承诺使用)。” -杰克阿奇博尔德(对我来说没有意义)
“调用堆栈”是当前正在执行的事情的列表:
function foo() {
debugger;
console.log('foo');
}
function bar() {
foo();
debugger;
}
bar();
当我们点击第一个调试器语句时,脚本仍在执行,如下所示bar
, as is foo
。由于存在父子关系,所以栈是script > bar > foo
。当我们点击第二个调试器语句时,foo
已经执行完毕,所以它不再在堆栈上。堆栈是script > bar
.
微任务队列被处理直到它为空,此时堆栈变空。
“事件循环的一次循环将恰好从宏任务队列(该队列在 WHATWG 规范中简称为任务队列)中处理一个任务。在该宏任务完成后,将处理所有可用的微任务,即相同的复飞周期。” - 堆栈溢出
Edit:我一直将上面的“宏任务”读作“微任务”。浏览器中实际上并不存在宏任务队列之类的东西,它只是一个任务队列。
虽然处理完任务后确实有一个微任务处理点,但它只是真正处理队列任务到队列微任务的规范,而不是先调用JS。大多数时候,当JS堆栈清空时,微任务队列也随之清空。
通过使用调试器逐步执行此代码片段,当处理/执行这些 .then(callback) 微任务时,执行堆栈不会显示为空。
执行回调时,堆栈永远不会为空,因为回调本身将位于堆栈上。但是,如果这是堆栈上的唯一内容,则可以假设在调用此回调之前堆栈为空。
Chrome 的开发工具试图帮助维护“异步”堆栈,但这不是真正的堆栈。真正的堆栈是第一个“异步”行之前的所有内容。
像 f2() 这样的常规函数是否被视为任务
作为任务或微任务不是函数的属性。可以在任务、微任务和事件循环的其他部分(例如渲染)中调用相同的函数。例如:
function foo() {}
// Here, I'll call foo() as part of the current task:
foo();
// Here, I'll let the browser call foo() in a future task:
setTimeout(foo);
// Here, I'll let the browser call foo() in a microtask:
Promise.resolve().then(foo);
// Here, I'll let the browser call foo() as part of the render steps:
requestAnimationFrame(foo);
在你的例子中,f2
is not在微任务中调用。有点像这样:
function three() {}
function two() {}
async function one() {
await two();
three();
}
one();
Here, one()
在执行脚本的任务中调用。one()
calls two()
同步,因此它作为同一任务的一部分运行。然后我们await
调用的结果two()
。因为我们await
,函数的其余部分在微任务中运行。three()
被调用,因此它在同一个微任务中运行。