深度剖析Js中的代码执行顺序

在JavaScript中,代码的执行顺序涉及事件循环(Event Loop)、调用栈(Call Stack)、任务队列(Task Queue)等核心机制。以下是分层次的解析和典型示例:

同步代码
  ↓
微任务队列(全部执行)
  ↓
渲染(如有需要)
  ↓
宏任务队列(取一个执行)
  ↓
重复循环

掌握这些规则后,可通过以下步骤分析任何执行顺序问题:

标记所有同步代码

识别微任务(Promise.then, MutationObserver, queueMicrotask)

识别宏任务(setTimeout, setInterval, I/O操作, UI渲染等)

按规则循环处理队列

复杂场景示例

async function async1() {
  console.log('async1 start'); // 同步
  await async2(); // 相当于Promise.resolve(async2()).then(...)
  console.log('async1 end'); // 微任务
}

async function async2() {
  console.log('async2'); // 同步
}

console.log('script start'); // 同步
async1();
new Promise(resolve => {
  console.log('Promise'); // 同步
  resolve();
}).then(() => {
  console.log('Promise then'); // 微任务
});
console.log('script end'); // 同步

/* 输出:
  script start → async1 start → async2 → Promise → script end →
  async1 end → Promise then
*/

这里核心概念修正:
await 的执行分为两个阶段:
1. await async2() 会立即执行 async2() 函数内的同步代码(即 console.log('async2')),
此时不会产生任何任务队列操作。
2. 隐式创建 Promise 并暂停 async1
   如果 async2() 返回非 Promise,JS 会用 Promise.resolve() 包装它
   await 的本质:将 async1 函数内await 之后的代码
(即 console.log('async1 end'))包装成这个 Promise 的 .then() 回调,也就是放入微任务队列

特殊场景注意

  1. Promise构造函数是同步的
new Promise(resolve => {
  console.log('Promise executor'); // 同步!
  resolve();
}).then(() => console.log('then')); // 微任务
  1. 任务队列的优先级
    宏任务队列中:
    交互事件(如click) > 网络回调 > 定时器
  2. Node.js差异
    Node中的process.nextTick优先级高于微任务:
Promise.resolve().then(() => console.log('Promise'));
process.nextTick(() => console.log('nextTick'));
// 输出:nextTick → Promise

再看一个例子

console.log('1');

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

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

console.log('6');

/* 答案:
  1 → 6 → 4 → 2 → 3 → 5
  执行流程:
  1. 同步代码:1, 6
  2. 微任务队列:4(执行时把5的定时器加入宏任务队列)
  3. 宏任务队列:2(执行时把3加入微任务队列)
  4. 微任务队列:3
  5. 宏任务队列:5
*/

再看一个例子【混合宏任务与嵌套 Promise】

console.log('Start');

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

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

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

console.log('End');
// Start → End → Promise 2 → Timeout 1 → Promise 1 → Timeout 2 → Timeout 3

关键点:

  1. 微任务中注册的宏任务:在微任务执行期间注册的宏任务(如 Timeout 3),会被添加到当前宏任务队列的末尾。
    它需要等待当前所有已存在的宏任务(如 Timeout 1 和 Timeout 2)执行完毕后才会执行。
  2. 执行顺序的本质:
同步代码 → 微任务 → 宏任务1 → 宏任务1触发的微任务 → 宏任务2 → 宏任务3...
  1. 为什么 Timeout 3 最后执行?
    因为它是前一个微任务(Promise 2)执行时才注册的,而 Timeout 1 和 Timeout 2 早已在同步阶段就注册了。

类比助记

把任务队列想象成一个快递分拣系统:

同步代码:立即处理的加急快递。

微任务:分拣员手头正在打包的包裹(必须全部打完才能处理新快递)。

宏任务:排队等待的普通快递。

在打包时新收到的快递(如 Timeout 3)会被放到普通队列的队尾,按顺序处理。

再来一个例子【混合事件循环优先级】

document.addEventListener('click', () => console.log('Click'));

setTimeout(() => console.log('Timeout'), 0);

fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(() => console.log('Fetch'));

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

// 模拟用户点击(实际点击时输出可能不同)
document.dispatchEvent(new Event('click'));
console.log('Sync end');
// Click → Sync end → Promise → Fetch → Timeout
//手动触发的 click 是同步的,真实点击会优先于 Fetch 和 Timeout。
//微任务 Promise 先于宏任务 Fetch 和 Timeout 执行。

Promise.all 与微任务

console.log('Start');

Promise.all([
  new Promise(resolve => {
    setTimeout(() => {
      console.log('Timeout 1');
      resolve();
    }, 0);
  }),
  Promise.resolve().then(() => console.log('Promise 1')),
]).then(() => {
  console.log('Promise.all resolved');
});

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

console.log('End');
// Start → End → Promise 1 → Promise 2 → Promise 3 → Timeout 1 → Promise.all resolved
//Promise.all 需等待所有子 Promise 完成,包括宏任务 Timeout 1。
//独立的微任务链(Promise 2 → Promise 3)优先执行。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容