JavaScript是怎样运行的?
同步代码,包括同步函数、表达式,以调用栈的方式执行;所有回调函数在任务会依次进入任务队列(Task Queue),依次等待执行,也就是 Event Loop。任务队列有多个, 可以分为两大类:Macro Tasks 和 Micro Tasks。下面以伪代码演示浏览器和 Node 中的 Event Loop 运行模型。
浏览器
// Event Loop可以看成程序的入口函数Main
while (true) {
// 任务队列的执行顺序是任意的,但同一来源的任务必须在同一队列中,同一队列中的任务严格按照进入队列的顺序执行。
// XHR请求,Web APIs,setTimeout,setInterval中的回调都会进入Macro Task队列。
queue = getNextQueue();
// 任务执行耗时如果太长,假设超过16ms,就会导致后面的重绘函数无法及时执行,画面就会出现卡顿掉帧现象。
task = queue.pop();
execute(task);
// 99.99%的Micro Task是Promise.resolve()。只有当所有的Promise.resolve()执行完后,才会进入重绘函数。所以如果有无限嵌套的,Promise.resolve(),
// 页面将会卡住,停止响应。
while (microtaskQueue.hasTasks()) doMicrotask();
if (isRepaintTime()) {
// 执行动画任务队列,也就是requestAnimationFrame中的回调函数。
animationTasks = animationQueue.copyTasks();
for (task in animationTasks) doAnimationTask(task);
// 执行重绘
repaint();
}
}
Node
Node 中的 Event Loop 模型相对简单,没有 Web API,没有动画队列。但多了 process.nextTick 这种 Micro Task。
while (tasksAreWaiting()) {
queue = getNextQueue();
while (queue.hasTasks()) {
task = queue.pop();
execute(task);
// setImediate()在nextTick()中执行
while (nextTickQueue.hasTasks()) {
doNextTickTask();
}
// Promise任务队列
while (promiseQueue.hasTasks()) {
doPromiseTask();
}
}
}