JavaScript事件循环机制

一、JavaScript执行机制基础

1. 单线程与异步

JavaScript设计之初就是为了在浏览器中处理DOM操作,为了避免复杂的线程同步问题,采用了单线程模型。这意味着JS代码是按顺序执行的,同一时间只能做一件事。

javascript 复制代码
console.log('1');
console.log('2');
// 输出顺序永远是1, 2

但单线程并不意味着阻塞,JavaScript通过事件循环机制实现了异步非阻塞的执行模式。

2. 调用栈与任务队列

  • 调用栈(Call Stack):存储同步任务的执行上下文,后进先出(LIFO)
  • 任务队列(Task Queue):存储异步任务的回调函数,先进先出(FIFO)

当调用栈为空时,事件循环会从任务队列中取出任务压入调用栈执行。

二、同步代码与异步代码

1. 同步代码执行

同步代码会立即进入调用栈执行:

javascript 复制代码
function func1() {
  console.log('func1');
}

function func2() {
  func1();
  console.log('func2');
}

func2();
// 输出顺序: func1, func2

2. 异步代码类型

常见的异步操作包括:

  • 定时器:setTimeout, setInterval
  • 网络请求:Ajax, Fetch
  • DOM事件:click, load等
  • Promise
  • async/await
javascript 复制代码
console.log('Start');

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

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

console.log('End');
// 输出顺序: Start, End, Promise, Timeout

三、宏任务与微任务

1. 宏任务(MacroTask)

包括:

  • js脚本执行事件
  • setTimeout setInterval
  • AJAX请求完成事件
  • 用户交互事件

2. 微任务(MicroTask)

包括:

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

3. 执行顺序规则

  1. 执行一个宏任务(通常是script整体代码)
  2. 执行过程中遇到微任务,加入微任务队列;遇到宏任务,加入宏任务队列
  3. 当前宏任务执行完毕,立即执行所有微任务
  4. 进行UI渲染(浏览器环境)
  5. 从宏任务队列取出下一个宏任务执行
javascript 复制代码
console.log('script start'); // 宏任务1开始

setTimeout(function() {
  console.log('setTimeout'); // 宏任务2
}, 0);

Promise.resolve().then(function() {
  console.log('promise1'); // 微任务1
}).then(function() {
  console.log('promise2'); // 微任务2
});

console.log('script end'); // 宏任务1结束

/*
输出顺序:
script start
script end
promise1
promise2
setTimeout
*/

四、典型面试题解析

题目1:基础执行顺序

javascript 复制代码
console.log('1');

setTimeout(function() {
  console.log('2');
  new Promise(function(resolve) {
    console.log('3');
    resolve();
  }).then(function() {
    console.log('4');
  });
}, 0);

new Promise(function(resolve) {
  console.log('5');
  resolve();
}).then(function() {
  console.log('6');
});

setTimeout(function() {
  console.log('7');
}, 0);

console.log('8');

解析过程

  1. 同步代码执行:

    • 输出1
    • 输出5(Promise构造函数是同步的)
    • 输出8
  2. 微任务队列:

    • then回调:输出6
  3. 宏任务队列:

    • 第一个setTimeout:
      • 输出2
      • Promise构造函数输出3
      • then回调(微任务):输出4
    • 第二个setTimeout:
      • 输出7

最终输出顺序:1, 5, 8, 6, 2, 3, 4, 7

题目2:async/await执行顺序

javascript 复制代码
async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}

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

console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

async1();

new Promise(function(resolve) {
  console.log('promise1');
  resolve();
}).then(function() {
  console.log('promise2');
});

console.log('script end');

解析过程

  1. 同步代码:

    • 输出script start
    • 调用async1(),输出async1 start
    • 调用async2(),输出async2
    • await后面的代码相当于Promise.then,放入微任务队列
    • Promise构造函数输出promise1
    • then回调放入微任务队列
    • 输出script end
  2. 微任务队列:

    • async1 end
    • promise2
  3. 宏任务队列:

    • setTimeout输出setTimeout

最终输出顺序

script start, async1 start, async2, promise1, script end, async1 end, promise2, setTimeout

相关推荐
Highcharts.js1 小时前
缺失数据可视化图表开发实战|Highcharts创建人员出生统计面积图表示例
开发语言·前端·javascript·信息可视化·highcharts·图表开发
测试员周周5 小时前
【Appium 系列】第16节-WebView-H5上下文切换 — 混合应用的自动化难点
运维·开发语言·人工智能·功能测试·appium·自动化·测试用例
杜子不疼.8 小时前
【C++ AI 大模型接入 SDK】 - DeepSeek 模型接入(上)
开发语言·c++·chatgpt
加号38 小时前
【C#】 串口通信技术深度解析及实现
开发语言·c#
sycmancia9 小时前
Qt——编辑交互功能的实现
开发语言·qt
石山代码9 小时前
C++ 内存分区 堆区
java·开发语言·c++
无风听海9 小时前
C# 隐式转换深度解析
java·开发语言·c#
放下华子我只抽RuiKe510 小时前
React 从入门到生产(四):自定义 Hook
前端·javascript·人工智能·深度学习·react.js·自然语言处理·前端框架
一只大袋鼠10 小时前
Git 进阶(二):分支管理、暂存栈、远程仓库与多人协作
java·开发语言·git
LuminousCPP11 小时前
数据结构 - 线性表第四篇:C 语言通讯录优化升级全记录(踩坑 + 思考)
c语言·开发语言·数据结构·经验分享·笔记·学习