JavaScript篇:JavaScript事件循环:从厨房看异步编程的奥秘

🎓 作者简介前端领域优质创作者

🚪 资源导航: 传送门=>

🎬 个人主页: 江城开朗的豌豆

🌐 个人网站: 江城开朗的豌豆 🌍

📧 个人邮箱: [email protected] 📩

💬 个人微信: y_t_t_t_ 📱

📌 座 右 铭: 生活就像心电图,一帆风顺就证明你挂了 💔

👥 QQ群: 906392632 (前端技术交流群) 💬

最近在面试前端工程师时,发现很多候选人对事件循环(Event Loop)的理解只停留在概念层面。这让他想起自己刚学习JavaScript时,也曾被setTimeout的"怪异"行为困扰。今天,我们就用一个厨房的比喻,彻底搞懂这个JavaScript核心机制。

厨房里的异步工作流

想象开了一家小餐馆,厨房的工作流程完美诠释了事件循环的原理:

  1. 厨师(主线程) :一次只能做一道菜(同步任务)
  2. 订单队列(任务队列) :新订单按顺序排队等待
  3. 定时器(计时器) :需要等待的菜品(如炖汤)
  4. 服务员(Web API) :处理外卖订单等异步任务
javascript 复制代码
console.log('开始营业'); // 同步任务 - 开门营业

setTimeout(() => {
  console.log('炖汤完成'); // 异步回调 - 需要时间的菜品
}, 2000);

Promise.resolve().then(() => {
  console.log('凉菜备好'); // 微任务 - 快速出餐的小菜
});

console.log('接待第一位客人'); // 同步任务 - 立即处理

输出顺序是:

javascript 复制代码
开始营业
接待第一位客人
凉菜备好
炖汤完成

事件循环的三层结构

画出了事件循环的核心组成:

  1. 调用栈(Call Stack) :正在执行的同步代码
  2. 任务队列(Task Queue) :setTimeout、DOM事件等宏任务
  3. 微任务队列(Microtask Queue) :Promise、MutationObserver等
javascript 复制代码
console.log('脚本开始'); // 1. 同步任务

setTimeout(() => {
  console.log('setTimeout'); // 4. 宏任务
}, 0);

Promise.resolve().then(() => {
  console.log('Promise'); // 3. 微任务
});

console.log('脚本结束'); // 2. 同步任务

真实场景中的执行顺序

在开发时遇到一个典型问题:

javascript 复制代码
document.querySelector('#btn').addEventListener('click', () => {
  console.log('点击事件开始'); // 宏任务
  
  Promise.resolve().then(() => {
    console.log('Promise微任务');
  });
  
  setTimeout(() => {
    console.log('setTimeout宏任务');
  }, 0);
  
  console.log('点击事件结束');
});

点击按钮后的输出顺序:

javascript 复制代码
点击事件开始
点击事件结束
Promise微任务
setTimeout宏任务

常见误区解析

误区1:setTimeout(fn, 0)会立即执行

曾经以为:

javascript 复制代码
setTimeout(() => {
  console.log('杨涛认为这会立即执行');
}, 0);

实际上,即使延迟为0,它仍然要等所有同步代码和微任务执行完后才会运行。

误区2:Promise比setTimeout快

看这个复杂例子:

javascript 复制代码
setTimeout(() => {
  console.log('宏任务1');
}, 0);

Promise.resolve().then(() => {
  console.log('微任务1');
  
  setTimeout(() => {
    console.log('宏任务2');
  }, 0);
});

Promise.resolve().then(() => {
  console.log('微任务2');
});

输出顺序:

javascript 复制代码
微任务1
微任务2
宏任务1
宏任务2

事件循环的浏览器实现

通过Chrome DevTools验证事件循环:

  1. 在Sources面板添加断点
  2. 在Console执行异步代码
  3. 使用Performance面板记录执行过程
javascript 复制代码
function test() {
  console.log('同步代码');
  
  setTimeout(() => {
    console.log('宏任务');
    debugger;
  }, 1000);
  
  Promise.resolve().then(() => {
    console.log('微任务');
  });
}

Node.js与浏览器的事件循环差异

在Node服务端开发时发现:

javascript 复制代码
setTimeout(() => {
  console.log('timer1');
  
  Promise.resolve().then(() => {
    console.log('promise1');
  });
}, 0);

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

浏览器输出:

javascript 复制代码
timer1
promise1
timer2

Node 11+版本输出相同,但旧版本可能不同。

实战应用技巧

1. 长任务分解

优化页面卡顿的方案:

javascript 复制代码
// 阻塞主线程的繁重任务
function processBigData() {
  // 分解为小块
  const chunkSize = 1000;
  let i = 0;
  
  function processChunk() {
    while (i < data.length && i % chunkSize !== 0) {
      // 处理数据...
      i++;
    }
    
    if (i < data.length) {
      setTimeout(processChunk, 0); // 让出主线程
    }
  }
  
  processChunk();
}

2. 优先级控制

javascript 复制代码
// 高优先级任务用Promise
function urgentTask() {
  Promise.resolve().then(doUrgentWork);
}

// 低优先级任务用setTimeout
function normalTask() {
  setTimeout(doNormalWork, 0);
}

总结

理解事件循环后,异步代码质量显著提升。关键要点:

  1. 同步任务立即执行,阻塞调用栈
  2. 微任务在当前宏任务结束后立即执行
  3. 宏任务在下次事件循环执行
  4. 渲染发生在宏任务之间

"事件循环就像餐厅运营,只有理解整个流程,才能高效协调各项工作。"这是在团队分享时的总结。掌握这一机制,你就能写出更高效的JavaScript代码。

相关推荐
极客小俊12 分钟前
粘性定位Position:sticky属性是不是真的没用?
前端
云端看世界15 分钟前
ECMAScript 类型转换 下
前端·javascript
云端看世界17 分钟前
ECMAScript 运算符怪谈 下
前端·javascript
云端看世界18 分钟前
ECMAScript 函数对象实例化
前端·javascript
前端爆冲19 分钟前
基于vue和flex实现页面可配置组件顺序
前端·javascript·vue.js
云端看世界20 分钟前
ECMAScript 中的特异对象
前端·javascript
il22 分钟前
Deepdive into Tanstack Query - 2.1 QueryClient 基础
前端
_十六24 分钟前
看完就懂!用最简单的方式带你了解 TypeScript 编译器原理
前端·typescript
Komorebi_999926 分钟前
Axios 是一个基于 Promise 的 HTTP 客户端,可用于浏览器和 Node.js 环境。以下是它的一些主要作用
javascript·ajax
云端看世界26 分钟前
ECMAScript 运算符怪谈 上
前端·javascript·ecmascript 6