【JavaScript 事件循环】

95 阅读3分钟

JavaScript 事件循环

总结图解(简单版)

同步代码 → 微任务队列 → 宏任务队列 → UI 渲染
           ↑            ↑
           └─> 事件循环 ─┘
  1. 同步代码执行完后,先执行微任务。
  2. 然后从宏任务队列中取出一个任务执行。
  3. 在每次执行完宏任务后,都会检查并执行微任务,直到微任务队列为空。
  4. 如果有必要,进行 UI 渲染

执行顺序:JavaScript 是单线程的,执行顺序分成同步任务和异步任务。 调用栈:同步任务会直接按顺序进入调用栈执行。 任务队列:异步任务会进入任务队列,分为微任务队列和宏任务队列。 微任务:优先级高,如 Promise.then、queueMicrotask。 宏任务:优先级低,如 setTimeout、setInterval、I/O 操作、UI 渲染、事件触发(如用户输入、点击等) 事件循环:每次调用栈清空后,事件循环会先执行所有微任务,再执行一个宏任务,循环往复

其他

JavaScript 是单线程执行的,意味着同一时刻只能执行一个任务。为了有效处理异步操作和避免阻塞主线程,JavaScript 引入了事件循环(Event Loop)机制,它协调了同步任务、异步任务以及 UI 渲染等操作。

基本流程

  1. 同步代码执行:

代码从上到下一行一行执行,遇到同步任务时,直接压入调用栈(Call Stack)并执行。

  1. 异步代码处理:

当遇到异步任务时(如 setTimeout、Promise 等),这些任务不会立刻执行,而是会被放入对应的队列中(宏任务队列或微任务队列)。

  1. 事件循环执行流程:
1.同步代码先执行完。
2.微任务队列中的任务优先执行,直到队列为空。
3.然后,宏任务队列中的任务依次执行。
4.在每次执行完一个宏任务后,都会检查微任务队列,执行所有微任务。
  1. UI 渲染:

每次执行完宏任务后,浏览器会进行 UI 渲染,但只有在宏任务执行完、微任务队列为空时才会进行渲染

一般会考,代码运行结果是啥

案例 01

setTimeout(() => {
  console.log(1);
}, 0);
const promise = new Promise((resolve, reject) => {
  console.log(2);
  reject(3); // 不会中断执行,还是会执行下面的4,除非 return reject(3)
  console.log(4);
});

promise
  .then(() => console.log(5))
  .catch(() => console.log(6)) // catch返回一个新的promise,并且 catch 中没有再抛错,所以返回的promise状态变成fulfilled,会被后面的then成功回调执行
  .then(() => console.log(7))
  .catch(() => console.log(8)) // 前面的catch和then都没有抛错,所以不会执行
  .then(() => console.log(9));
console.log(10);

// 2, 4, 10 (同步任务),  5, 7, 9,(微任务) 1(宏任务)

案例 02

function test() {
  console.log(1);
  setTimeout(function () {
    // timer1
    console.log(2);
  }, 1000);
}

test();

setTimeout(function () {
  // timer2
  console.log(3);
});

new Promise(function (resolve) {
  console.log(4);
  setTimeout(function () {
    // timer3
    console.log(5);
  }, 100);
  resolve();
}).then(function () {
  setTimeout(function () {
    // timer4
    console.log(6);
  }, 0);
  console.log(7);
});

console.log(8);

// 1, 4, 8 (同步任务), 7(微任务),
// 3(宏任务,它的时间虽然是0,但是它在最前面,js 是从上往下一行一行执行), 6(宏任务), 5(宏任务), 2(宏任务)

案例 03

async function async1() {
  console.log(1);
  await async2();
  console.log(2);
}

async function async2() {
  console.log(3);
}

console.log(4);

setTimeout(() => {
  console.log(5);
}, 0);

async1();

new Promise((resolve, reject) => {
  console.log(6);
  resolve();
}).then(() => {
  console.log(7);
});

console.log(8);

// 4, 1, 3, 6, 8(同步任务), 2(微任务), 7(微任务), 5(宏任务)

上面里面一部分代码

async function async1() {
  console.log(1);
  await async2();
  console.log(2);
}

等价于;

async function async1() {
  console.log(1);
  async2().then(() => {
    console.log(2);
  });
}

案例 05

setTimeout(() => {
  console.log("A");
  Promise.resolve().then(() => {
    console.log("B");
  });
}, 1000);

Promise.resolve().then(() => {
  console.log("C");
});

new Promise((resolve) => {
  console.log("D");
  resolve("");
}).then(() => {
  console.log("E");
});

async function sum(a, b) {
  console.log("F");
}

async function asyncSum(a, b) {
  await Promise.resolve();
  console.log("G");
  return Promise.resolve(a + b);
}

sum(3, 4);
asyncSum(3, 4);
// D, F, C, E, G, A, B

案例 06

console.log("script start"); // 1

setTimeout(function () {
  console.log("setTimeout"); // 5
}, 0);

new Promise((res, rej) => {
  console.log("promise"); //  2
  rej(); // 这里拒绝了啊
})
  .then(function () {
    console.log("promise1");
  })
  .catch(function () {
    return 1;
  })
  .then(function () {
    console.log("promise2"); // 4
  });

console.log("script end"); // 3