0基础进大厂,第24天:JS中的事件循环

6 阅读3分钟

引言

大家好!今天晚上我们来深入聊聊JavaScript中一个超级重要的概念——事件循环(Event Loop)。理解了它,你就能真正明白JS代码的执行顺序,再也不会被setTimeoutPromise的执行顺序搞晕了!废话不多说,直接开始!

基础篇——JS执行机制

1. JavaScript是单线程的

console.log('第一行代码');
alert('阻塞操作'); // 这行代码会阻塞后续代码执行
console.log('第二行代码');

关键点:

  • JS只有一个主线程
  • 所有同步任务按顺序执行
  • 遇到耗时操作会阻塞后续代码

2. 为什么需要事件循环?

因为浏览器需要:

  • 处理用户交互
  • 执行JS代码
  • 渲染页面
  • 处理网络请求

所有这些任务都需要排队执行!

核心概念

1. 调用栈(Call Stack)

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

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

foo();

调用栈变化过程:

  1. foo()入栈
  2. console.log('foo')入栈 → 执行 → 出栈
  3. bar()入栈
  4. console.log('bar')入栈 → 执行 → 出栈
  5. bar()出栈
  6. foo()出栈

2. 任务队列(Task Queue)

JS中有两种主要队列:

  • 宏任务队列(MacroTask Queue)

    • setTimeout
    • setInterval
    • I/O操作
    • UI渲染
    • setImmediate(Node.js)
  • 微任务队列(MicroTask Queue)

    • Promise.then/catch/finally
    • MutationObserver
    • process.nextTick(Node.js)

3. 事件循环工作流程

  1. 执行同步代码(调用栈)
  2. 执行完所有同步代码后,检查微任务队列
  3. 执行所有微任务
  4. 如有必要,渲染页面
  5. 从宏任务队列取出一个任务执行
  6. 重复上述过程

实战分析

案例1:基础执行顺序

console.log('1');

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

Promise.resolve().then(() => {
  console.log('3');
});

console.log('4');

输出顺序:

  1. '1'
  2. '4'
  3. '3'
  4. '2'

案例2:嵌套Promise

console.log('1');

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

Promise.resolve().then(() => {
  console.log('3');
  Promise.resolve().then(() => {
    console.log('4');
  });
});

console.log('5');

输出顺序:

  1. '1'
  2. '5'
  3. '3'
  4. '4'
  5. '2'

案例3:复杂场景

console.log('1');

setTimeout(() => {
  console.log('2');
  Promise.resolve().then(() => {
    console.log('3');
  });
}, 0);

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

Promise.resolve().then(() => {
  console.log('5');
  setTimeout(() => {
    console.log('6');
  }, 0);
});

console.log('7');

输出顺序:

  1. '1'
  2. '7'
  3. '5'
  4. '2'
  5. '3'
  6. '4'
  7. '6'

进阶理解

1. 为什么微任务优先级高?

因为微任务通常与当前执行的JS代码直接相关(如Promise回调),需要在下一个宏任务执行前完成。

2. requestAnimationFrame

console.log('1');

requestAnimationFrame(() => {
  console.log('2');
});

Promise.resolve().then(() => {
  console.log('3');
});

console.log('4');

requestAnimationFrame的执行时机在渲染之前,微任务之后。

3. Node.js与浏览器差异

在Node.js中:

  • process.nextTick比微任务优先级更高
  • setImmediatesetTimeout的执行顺序有特殊规则

常见误区

1. "setTimeout(fn, 0)会立即执行"

错!它只是把回调放入宏任务队列,等待当前调用栈清空后执行。

2. "Promise比setTimeout快"

不完全正确。Promise回调是微任务,而setTimeout是宏任务,执行时机不同。

3. "所有异步代码都是宏任务"

错!Promise、MutationObserver等是微任务。

总结

记住事件循环的核心规则:

  1. 同步代码最先执行
  2. 微任务在同步代码执行完后立即执行
  3. 宏任务在微任务执行完后执行
  4. 每次执行一个宏任务后,会再次检查微任务队列

理解了这些,你就能准确预测任何JS代码的执行顺序了!

终极测试

console.log('1');

setTimeout(() => {
  console.log('2');
  Promise.resolve().then(() => {
    console.log('3');
  });
}, 0);

new Promise((resolve) => {
  console.log('4');
  resolve();
}).then(() => {
  console.log('5');
  setTimeout(() => {
    console.log('6');
  }, 0);
});

console.log('7');

答案:

  1. '1'
  2. '4'
  3. '7'
  4. '5'
  5. '2'
  6. '3'
  7. '6'

你答对了吗?