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

相关推荐
xiaoye-duck5 小时前
《C++ 项目:手写大模型接入 SDK》Gemini接口设计(上):初始化、全量响应实现和测试
开发语言·网络·c++·语言模型
nice_lcj5207 小时前
Java Lambda 表达式入门:怎么写、怎么用、常用方法有哪些
java·开发语言·后端·面试
码农小河668 小时前
基于SSM+JSP+MySQL的在线医疗服务系统的设计与实现
java·开发语言·mysql
系统集成-鹰翔8 小时前
ERP图片改名后仍命中识别缓存:缓存键、十分钟到期与调用计数
javascript·经验分享·后端
FlyWIHTSKY10 小时前
vue2和vue3有什么区别,用法上有哪些区别
前端·javascript·vue.js
程序员大阳11 小时前
副队长JavaScript教程(66)-本地存储实战:待办事项网页的开发
javascript·json·实战·待办事项·本地存储·案例
Dream-Y.ocean12 小时前
鸿蒙PC平台逻控 PLC 编辑器适配实战:基于 Electron 壳方案的纯 JavaScript 梯形图编程与仿真引擎开发
javascript·编辑器·harmonyos
nice_lcj52012 小时前
ThreadLocal 入门:它是怎么隔离数据的、为什么会导致内存泄漏
java·开发语言·后端·面试·并发编程·内存泄漏·threadlocal
紧跟先前的步伐12 小时前
Go AI Agent 实战 28|Agent 的预算、超时与停止条件
开发语言·人工智能·后端·ai·golang
bksczm13 小时前
InnoDB 行格式详解:DYNAMIC / COMPACT / COMPRESSED / REDUNDANT
java·开发语言·数据库