什么是JavaScript 中的Event Loop?

98 阅读2分钟

Event Loop 对许多开发者来说有点疑惑,但它是JavaScript 引擎很重要的组成部分。它使得JavaScript作为单线程仍然能够支持非阻塞方式运作。为了明白 Event Loop,我们需要解释一下JavaScript 引擎中的一些内容,比如: 调用栈、微任务和他们各自的队列。

调用栈

调用栈是一个数据结构,用来追踪JavaScript代码执行的链路。正如名字所表明的,它是一个栈,因此在内存中是一个后进先出的数据结构。

function foo() {
    console.log('foo');
    bar();
}

function bar() {
    console.log('bar');
}

1、调用栈一开始是空 2、foo() 被压入栈中 3、foo() 执行并且在栈中pop 4、console.log("foo") 压到栈中 5、console.log("foo") 执行并且在栈中pop 6、bar() 被pushed到栈中 7、bar() 执行并且在栈中pop 8、console.log("bar") 被pushed onto the call Stack。 9、console.log("bar") 执行并且在栈中pop。 10、The call stack现在是空。

Tasks 和 Task Queue

Tasks 是计划的,同步的代码块。当执行的时候,他们对调用栈有排他性的占用,并且可以enqueue 其他任务。在不同的 task 之间,浏览器可以执行渲染更新,任务是被存储在Task 队列中,等待他们相关的方法的执行。 一个Tasks的示例为 event监听时间的 event的回调函数,以及 setTimeout()的回调函数。

Microtasks 和 Microtask Queue

Microtasks 和 task 类似,他们也是计划的,同步的代码块,执行时也是排他的获取调用栈的资源。此外,他们保存在他们自己的队列中, Microtask Queue。 Microtasks和Tasks不同 在于 Microtask 队列必须在Task 完成后,re-rendering之前。 比如 Microtasks 包括 Promise 的callbacks 调用以及 MutationObserver回调。

The Event Loop

最后,Event Loop 是一个Loop 一直在运行和校验调用栈是否已经清空。 它将Task和Microtasks 设置到调用栈中,并且控制渲染进程。 它由下面几个关键步骤组成: 1、脚本的执行: 同步的执行脚本,直到调用栈已经清空。 2、Task processing:选择Task Queue中的第一个Task并且运行它,直到回调栈已经清空。 3、Microtasks processing:选择Microtask Queue中的第一个Microtask直到回调栈已经清空。 4、Rendering: Re-renderUI并且loop到第二步 一个实际的案例:

console.log('Script start');

setTimeout(() => console.log('setTimeout()'), 0);

Promise.resolve()
  .then(() => console.log('Promise.then() #1'))
  .then(() => console.log('Promise.then() #2'));

console.log('Script end');

// LOGS:
//   Script start
//   Script end
//   Promise.then() #1
//   Promise.then() #2
//   setTimeout()

总结:

  • Event Loop 负责执行Javascript代码,它先执行脚本,然后执行Tasks和Microtasks
  • Tasks 和 Microtasks 是被控制的,同步的代码块,他们一次只能执行一个。各自放在Task Queue 和 Microtask Queue中。
  • 对所有这些内容来说,调用栈被用来控制控制方法的调用关系。
  • 无论什么时候 microtasks被执行,它都必须在下次Task执行之前清空。
  • Rendering在 Tasks之间渲染、并非在Microtasks之间。

原文引用: www.30secondsofcode.org/js/s/event-…