七娃笔记-补充:

js的任务分为同步任务、异步任务,而异步任务里面又分宏任务、微任务。


宏任务

script标签中的全部代码、setTimeout、setInterval、setImmediate(浏览器暂时不支持,只有IE10支持,具体可见MDN、I/O、UI Rendering。

微任务
Promise 中的then/catch、async / await、Process.nextTick(Node独有)、Object.observe(废弃)、MutationObserver

事件循环理解

执行顺序:同步任务=》微任务=》宏任务=》判断是否存在同步任务,有则继续循环。

————————————————————————————————————————————————————————

事件循环是许多开发人员混淆的根源,但它是 JavaScript 引擎的基本部分。它允许JavaScript是单线程的,但能够以非阻塞的方式执行。要理解事件循环,我们首先需要解释一些关于 JavaScript 引擎的事情,例如调用堆栈、任务、微任务及其各自的队列。让我们一一分解。

【转载】JavaScript 中的事件循环是什么?-QUI-Notes

调用堆栈

调用堆栈是一种数据结构,用于跟踪 JavaScript 代码的执行。顾名思义,它是一个堆栈,因此是内存中的 LIFO(后进先出)数据结构。执行的每个函数都表示为调用堆栈中的一个帧,并放置在前一个函数的顶部。

让我们一步一步看一个简单的例子:

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

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

  1. 调用堆栈最初为空。
  2. 该函数被推送到调用堆栈上。foo()
  3. 该函数将执行并弹出调用堆栈。foo()
  4. 该函数被推送到调用堆栈上。console.log('foo')
  5. 该函数将执行并弹出调用堆栈。console.log('foo')
  6. 该函数被推送到调用堆栈上。bar()
  7. 该函数将执行并弹出调用堆栈。bar()
  8. 该函数被推送到调用堆栈上。console.log('bar')
  9. 该函数将执行并弹出调用堆栈。console.log('bar')
  10. 调用堆栈现在为空。

任务和任务队列

任务是计划的同步代码块。执行时,它们对调用堆栈具有独占访问权限,还可以将其他任务排队。在任务之间,浏览器可以执行呈现更新。任务存储在任务队列中,等待其关联函数执行。反过来,任务队列是 FIFO(先进先出)数据结构。任务的示例包括与事件关联的事件侦听器的回调函数和setTimeout().

微任务和微任务队列

微任务与任务类似,因为它们是计划的同步代码块,在执行时具有对调用堆栈的独占访问权限。此外,它们存储在自己的FIFO(先进先出)数据结构中,即微任务队列。但是,微任务与任务的不同之处在于,微任务队列必须在任务完成后和重新渲染之前清空。微任务的例子包括Promise回调和回调。MutationObserver

微任务和微任务队列也称为作业和作业队列。

事件循环

最后,事件循环是一个保持运行并检查调用堆栈是否为空的循环。它通过一次将任务和微任务放置在调用堆栈中来处理它们,并控制渲染过程。它由四个关键步骤组成:

脚本评估:同步执行脚本,直到调用堆栈为空。
任务处理:选择任务队列中的第一个任务并运行它,直到调用堆栈为空。
微任务处理:选择微任务队列中的第一个微任务并运行它,直到调用堆栈为空,重复直到微任务队列为空。
渲染:重新呈现 UI 并循环回步骤 2。

一个实际的例子
为了更好地理解事件循环,让我们看一个包含上述所有概念的实际示例:

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()

输出是否与预期相符?让我们一步一步地分解正在发生的事情:

  1. 调用堆栈最初为空。事件循环开始计算脚本。
  2. console.log()被推送到调用堆栈并执行,记录 .'Script start'
  3. setTimeout()被推送到调用堆栈并执行。这将在任务队列中为其回调函数创建一个新任务。
  4. Promise.prototype.resolve()被推送到调用堆栈并执行,依次调用Promise.prototype.then().
  5. Promise.prototype.then()被推送到调用堆栈并执行。这会在微任务队列中为其回调函数创建一个新的微任务。
  6. console.log()被推送到调用堆栈并执行,记录 .'Script end'
  7. 事件循环已完成其当前任务,正在评估脚本。然后它开始运行微任务队列中的第一个微任务,这是Promise.prototype.then()在步骤 5 中排队。
  8. console.log()被推送到调用堆栈并执行,记录 .'Promise.then() #1'
  9. Promise.prototype.then()被推送到调用堆栈并执行。这将在微任务队列中为其回调函数创建一个新条目。
  10. 事件循环检查微任务队列。由于它不为空,它执行第一个微任务,这是Promise.prototype.then()在步骤 10 中排队。
  11. console.log()被推送到调用堆栈并执行,记录 .'Promise.then() #2'
  12. 如果有的话,将在此处进行重新渲染。
  13. 微任务队列为空,因此事件循环移动到任务队列并执行第一个任务,这是setTimeout()在步骤 3 中排队。
  14. console.log()被推送到调用堆栈并执行,记录 .'setTimeout()'
  15. 如果有的话,将在此处进行重新渲染。
  16. 调用堆栈现在为空。

总结

事件循环负责执行 JavaScript 代码。它首先评估并执行脚本,然后处理任务和微任务。
任务和微任务是计划的同步代码块。它们一次执行一个,并分别放置在任务队列和微任务队列中。
对于所有这些,调用堆栈用于跟踪函数调用。
每当执行微任务时,必须先清空微任务队列,然后才能执行下一个任务。
渲染发生在任务之间,但不发生在微任务之间。

笔记

事件循环的脚本评估步骤本身的处理方式与任务类似。
的第二个论点setTimeout()表示执行前的最短时间,而不是保证时间。这是因为任务按顺序执行,而微任务可能在两者之间执行。
Node.js 中事件循环的行为类似,但存在一些差异。最值得注意的是,没有渲染步骤。
较旧的浏览器版本不完全遵守操作顺序,因此任务和微任务可能以不同的顺序执行。

原文地址:https://www.30secondsofcode.org/articles/s/javascript-event-loop-explained