liangshanbo1215头像
关注

JavaScript 异步核心主线

主线程发起异步操作 → 宿主环境处理 → 异步完成后产生任务 → 进入相应的任务调度队列 → 事件循环选择合适的任务执行 → 回到主线程执行回调。

JavaScript 主线程
      │
      │ 发起异步操作
      ↓
宿主环境处理
      │
      │ 异步操作完成
      ↓
产生后续任务
      │
      ↓
不同任务来源 / 调度队列
      │
      ↓
事件循环选择可运行任务
      │
      ↓
JavaScript 主线程
      │
      ↓
执行任务中的 JavaScript

这条主线才是第一性原理。


一、追问:任务是不是只有一个队列?

不是。 现代浏览器规范不是简单的:

一个宏任务队列
+
一个微任务队列

而是:

不同 Task Source
       ↓
一个或多个 Task Queue
       ↓
Event Loop
       ↓
选择可运行的 Task

例如浏览器会区分不同来源的任务:

用户交互任务
定时器任务
网络相关任务
DOM 相关任务
其他任务
   ↓
浏览器进行调度

不同任务之间并不是简单按照“谁先进队列谁永远先执行”。

HTML Standard 明确允许用户代理根据情况选择不同任务队列,例如可以提高鼠标、键盘交互相关任务的处理优先级,以保证页面响应性。(HTML Living Standard)


二、那 Promise 到底是什么?

Promise 是一种异步抽象;Promise 的 .then()、.catch()、.finally() 回调会通过微任务机制执行。

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

核心过程:

Promise 状态完成
      ↓
then 回调安排为微任务
      ↓
当前 JavaScript 执行结束
      ↓
执行微任务
      ↓
A

Promise 的回调使用微任务队列,这是当前 Web 平台模型的一部分。(MDN Web Docs)


三、那微任务是什么?

核心结论

微任务用于把一段 JavaScript 安排到当前任务结束后、下一次任务开始前执行。

例如:

console.log(1);

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

console.log(3);

结果:

1
3
2

因为:

当前任务
 ↓
1
 ↓
注册微任务
 ↓
3
 ↓
当前任务结束
 ↓
执行微任务
 ↓
2

而且:

微任务执行过程中产生的新微任务,也会继续执行,直到微任务队列清空。 (MDN Web Docs)


四、setTimeout(0) 应该怎么理解?

setTimeout 到期后,会将对应的任务加入定时器相关的任务调度流程;它不是“0 毫秒后立即执行”。

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

应该理解成:

注册定时器
    ↓
满足定时条件
    ↓
产生 timer task
    ↓
进入相应任务队列
    ↓
等待事件循环调度
    ↓
最终执行回调

所以:

0 表示最小延迟,不表示立即执行。

HTML Standard 对 setTimeout() 产生任务的描述也是基于 task,而不是“立即执行回调”。(HTML Living Standard)


五、Promise 和 setTimeout(0) 谁先?

这个问题仍然可以回答:

在典型浏览器场景下,Promise 回调会先于 setTimeout(0) 回调执行。

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

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

通常:

B
A

但高级面试应该说:

Promise 回调进入微任务队列;当前任务结束后会执行微任务检查,而定时器产生的 task 需要等待后续事件循环调度。


六、事件循环到底干什么?

这是整套模型的核心。

事件循环不断运行调度流程,从当前可运行的任务中选择合适的任务执行,并在任务结束后处理微任务等后续步骤。

简化理解:

事件循环
   ↓
选择一个可运行任务
   ↓
执行任务
   ↓
处理微任务
   ↓
浏览器根据需要进行渲染等工作
   ↓
继续下一轮调度

MDN 对现代浏览器运行时的概括也是:事件循环每轮执行一个任务,然后处理微任务,再根据需要进行渲染。(MDN Web Docs)


七、所以整套知识应该这样记

                 JavaScript 异步
                       │
                       ↓
              主线程发起异步操作
                       │
                       ↓
                 宿主环境处理
                       │
                       ↓
                异步操作完成
                       │
                       ↓
                 产生后续任务
                       │
                       ↓
          ┌────────────┴────────────┐
          ↓                         ↓
    不同 Task Source           微任务机制
          ↓                         ↓
    不同任务调度队列          Promise 等回调
          │                         │
          └──────────┬──────────────┘
                     ↓
                  事件循环
                     ↓
              选择可运行任务
                     ↓
              执行 JavaScript
                     ↓
                处理微任务
                     ↓
             下一轮事件循环

最终面试版

JavaScript 异步的核心不是“宏任务和微任务”,而是任务调度。

JavaScript 主线程发起异步操作后,交给宿主环境处理;异步操作完成后产生后续任务,由事件循环根据任务来源、可运行状态等进行调度,最终回到 JavaScript 主线程执行。

Promise 的回调属于微任务机制,会在当前任务结束后的微任务检查阶段执行;setTimeout(0) 则是在定时条件满足后产生相应任务,等待事件循环后续调度,所以并不是立即执行。

现代浏览器并不是简单的“一个宏任务队列 + 一个微任务队列”,而是存在不同的任务来源和任务队列,浏览器可以根据调度策略选择合适的任务。 (HTML Living Standard)

你真正应该记住的主线

主线程 → 宿主处理 → 产生任务 → 任务调度 → 事件循环 → 主线程执行 → 微任务检查

这比传统的:

宏任务 → 微任务 → 事件循环

更接近现代浏览器实际的调度模型。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/liangshanbo1215/article/details/167127749

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--