JavaScript中同步任务和异步任务的区别是什么,什么是微任务和宏任务:深入理解事件循环机制

前言

    在JavaScript的世界中,同步任务异步任务的协同工作是实现高效非阻塞编程的核心。无论是处理用户点击事件、发起网络请求,还是执行定时操作,理解它们的运行机制都是每位开发者的必修课。

同步任务

什么是同步任务

    同步任务(Synchronous Task)是立即在主线程执行栈中顺序执行的代码。它们像流水线上的工人,必须等待前一个任务完成后才能开始下一个任务。

同步任务有哪些

常见的同步任务有:

  • 普通函数的调用,例如console.log
  • 变量的声明,赋值
  • 同步的代码逻辑。例如(if,for等控制流语句)

同步任务的特点

  • 确定性:同步任务会按照代码的顺序依次执行,意味着前一个任务必须全部执行完毕,后一个任务才会开始执行。
    console.log("第一步:准备食材"); // 同步
    console.log("第二步:开始烹饪");  // 同步
    console.log("第三步:装盘上菜");  // 同步
    // 输出顺序严格按照代码书写顺序执行
  • 阻塞性:若有一个非常耗时的同步任务,会阻塞主线程,导致页面卡顿
    console.log("开始计算");
    for(let i=0; i<3e9; i++){} // 模拟耗时操作
    console.log("计算完成");    // 页面会在这期间卡死
  • 简单性:同步任务的执行顺序固定,代码从上到下依次执行,这种执行模式使得代码的逻辑更加清晰易懂。对于简单的任务,同步代码往往更加直观和易于维护。

异步任务

为什么需要异步任务

当遇到网络请求(约200ms)、文件读取(约500ms)等耗时操作时,同步模式会导致:

  1. 浏览器界面冻结

  2. 用户体验恶化

  3. 资源利用率低下

异步任务

  • 定义:不立即执行的任务,会被放入任务队列(分为宏任务队列和微任务队列),等待主线程空闲时处理。

异步任务的分类

宏任务

定义:由浏览器或 Node.js 环境提供的、需要排队等待执行的任务。

常见场景:

  • setTimeout / setInterval(定时器)
  • DOM 事件回调(如点击、滚动)
  • I/O 操作(如文件读写)
  • requestAnimationFrame(浏览器)
  • setImmediate(Node.js)

执行机制:
每次事件循环(Event Loop)会从宏任务队列中取出一个任务执行。
执行完毕后,检查微任务队列并清空所有微任务,再进行下一轮循环。

微任务

定义:优先级高于宏任务的任务,通常与 Promise 相关。

常见场景

  • Promise.then() /Promise.catch()

  • MutationOberver(监听 DOM 变化)

  • queueMincrotask  (显式添加微任务)

  • process.nextTick()  Node.js,优先级最高)

执行机制

在当前同步代码执行完毕后,立即清空所有微任务

如果在微任务中又产生了新的微任务,这些新微任务也会被立即执行,直到队列为空。

经典异步场景

console.log("同步任务 1");

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

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

console.log("同步任务 2");

// 输出顺序:
// 同步任务 1 → 同步任务 2 → 微任务(Promise) → 宏任务(setTimeout)

事件循环机制

核心机制:

  1. 执行同步代码:优先执行当前执行栈(Call Stack)中的所有同步任务。

  2. 检查微任务队列:执行所有微任务(如 Promise.thenMutationObserver),直到队列清空。

  3. 渲染页面(浏览器环境):更新 DOM、执行 requestAnimationFrame 回调等(仅浏览器)。

  4. 执行一个宏任务:从宏任务队列(如 setTimeoutDOM 事件)中取出一个任务执行。

  5. 循环往复:重复上述步骤。

从代码看执行顺序

混合任务示例:

setTimeout(() => console.log("宏任务1"), 0);

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

Promise.resolve()
  .then(() => console.log("微任务2"));

console.log("同步任务");

/* 输出顺序:
   同步任务 → 微任务1 → 微任务2 → 宏任务1 → 宏任务2 */

Node.js的特殊情况: 

setImmediate(() => console.log("setImmediate"));
setTimeout(() => console.log("setTimeout"), 0);

process.nextTick(() => console.log("nextTick"));
Promise.resolve().then(() => console.log("Promise"));

// 输出顺序:
// nextTick → Promise → setTimeout → setImmediate

微任务中产生新的微任务:

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

// 输出顺序:微任务 1 → 微任务 2

微任务阻塞宏任务 :

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

Promise.resolve().then(() => {
  while (true) {} // 无限循环,阻塞后续所有任务
});

// 永远不会输出 "宏任务"

常见问题

setTimeout(fn,0)真的0秒执行吗?

  • 实际会有至少4ms的延迟(HTML5规范)
  • 受事件循环状态影响

微任务会阻塞渲染吗?

document.body.style.backgroundColor = 'red';
Promise.resolve().then(() => {
  for(let i=0; i<3e9; i++){} // 页面保持红色无法更新
});
document.body.style.backgroundColor = 'blue';

如何避免异步陷阱?

// 错误示例
for(var i=0; i<5; i++){
  setTimeout(() => console.log(i), 0); // 输出5个5
}

// 正确方案
for(let i=0; i<5; i++){ // 使用let块级作用域
  setTimeout(() => console.log(i), 0); // 0-4
}
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值