惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

J
Java Code Geeks
博客园 - 聂微东
人人都是产品经理
人人都是产品经理
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园_首页
量子位
阮一峰的网络日志
阮一峰的网络日志
酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hackread – Cybersecurity News, Data Breaches, AI and More
云风的 BLOG
云风的 BLOG
D
DataBreaches.Net
B
Blog
L
LangChain Blog
Apple Machine Learning Research
Apple Machine Learning Research
Vercel News
Vercel News
博客园 - 三生石上(FineUI控件)
爱范儿
爱范儿
Microsoft Azure Blog
Microsoft Azure Blog
IT之家
IT之家
aimingoo的专栏
aimingoo的专栏
B
Blog RSS Feed
H
Help Net Security
The Cloudflare Blog
U
Unit 42

博客园 - lzhdim

25个每个开发人员都应该掌握的JavaScript 数组方法 20260831 - 个人小作品更新 C#使用SQLite数据库例子 - 开源研究系列文章 20260830 - 个人小作品更新 20260829 - 个人小作品更新 首款媲美分体水冷的一体水冷!微星CORELIQUID E15 360水冷散热器评测:目前为止 锐龙9 9950X3D最强性能释放 JavaScript常见的内存泄露问题 - JavaScript学习系列文章 C#开发的颜色取色器 - 开源研究系列文章 - 个人小作品 成长环境 - 我的闪存 提高 SQL 语句执行速度的方法 JavaScript安全最佳实践 关闭iPhone一些默认设置,让iPhone更流畅 20260814 - 个人小作品更新 JavaScript对象与元编程 20260812 - 个人小作品更新 JavaScript 模块化系统 Markdown 语法完全指南 C# 通过 Windows API 实现进程内存读写操作 JavaScript 函数进阶课题 C#开发的基于钩子技术监测键鼠事件的例子 - 开源项目研究文章 JavaScript异步编程的演进 近十年最帅风冷!酷冷至尊V8 ACE 3DHP散热器评测:单塔能干翻双塔 20260723 - 个人小作品更新 C# 中读取CPU、硬盘和内存温度 雷柏VT3s MAX鼠标评测:中小手型用户也没有被遗忘 顶尖性能的小号电竞鼠标来了 JavaScript 中的原型与继承 1分钟查清局域网所有IP 20260714 - 个人小作品更新 WorkBuddy 完整教程手册 专为AI而生的企业级QLC SSD!长江存储PE501 61.44TB评测:超过1PB的写入测试
13、JavaScript事件循环机制 - JavaScript学习系列文章
lzhdim · 2026-08-26 · via 博客园 - lzhdim

一、为什么需要事件循环?

首先, 咱们得明白JavaScript是单线程的. 啥意思呢? 就是说JS在同一时间只能做一件事, 不能像Java那样开多个线程同时干活. 这听起来是不是很弱鸡? 别急, 设计成这样是有原因的. 

想象一下, 如果JS是多线程的, 你在一个线程里修改DOM, 另一个线程里删除同一个DOM, 那浏览器该听谁的? 这不就乱套了吗? 为了避免这种混乱, JS选择了单线程.

但单线程也有个问题--如果遇到耗时操作(比如网络请求), 整个页面不就卡死了吗? 这时候事件循环机制就派上用场了! 它让JS虽然只有一个主线程, 但能够"看起来"同时处理多个任务. 

二、事件循环机制

事件循环机制主要由三部分组成: 

1. 调用栈(Call Stack): 这是JS执行代码的地方, 你可以把它想象成一个盘子, 后放进去的先拿出来(后进先出)

2. 任务队列(Task Queue): 等待执行的任务排成的队伍

3. 事件循环(Event Loop): 负责盯着调用栈和任务队列的"监工"

咱们来看个简单的例子: 

function multiply(a, b) {    return a * b;}
function square(n) {    return multiply(n, n);}
function printSquare(n) {    const squared = square(n);    console.log(squared);}
printSquare(4);

执行过程是这样的: 

1. printSquare(4)入栈

2. printSquare内部调用square(4)square(4)入栈

3. square内部调用multiply(4,4)multiply(4,4)入栈

4. multiply执行完返回16,出栈

5. square返回16,出栈

6. console.log(16)入栈,执行完出栈

7. printSquare执行完,出栈

整个过程就像叠盘子一样, 后进的先出. 

那异步代码怎么执行呢? 看这个例子: 

console.log('Hi');
setTimeout(function cb() {    console.log('There');}, 5000);
console.log('JS');

输出顺序是: 'Hi' -> 'JS' -> 'There'

执行过程: 

1. console.log('Hi')入栈, 执行完出栈

2. 遇到setTimeout, 这是Web API提供的功能, JS把它交给浏览器处理, 同时注册回调函数cb

3. console.log('JS')入栈, 执行完出栈

4. 5秒后, 浏览器把cb放入任务队列

5. 事件循环发现调用栈空了, 就把cb从任务队列拿到调用栈执行

三、微任务与宏任务 

这才是重点! 面试常考! JS的任务其实分为两种:

  • 宏任务(Macrotask): 包括script整体代码、setTimeout、setInterval、I/O、UI渲染等. 

  • 微任务(Microtask): 包括Promise.then、MutationObserver、process.nextTick(Node环境). 

它们执行顺序的区别很重要: 

1. 执行一个宏任务(初始就是整个script)

2. 执行过程中遇到微任务,就加入微任务队列; 遇到宏任务,就加入宏任务队列

3. 当前宏任务执行完, 立即执行所有微任务(直到微任务队列清空)

4. 进行浏览器渲染(如果需要)

5. 开始下一个宏任务

来看个经典例子: 

console.log('script start');
setTimeout(function() {    console.log('setTimeout');}, 0);
Promise.resolve().then(function() {    console.log('promise1');}).then(function() {    console.log('promise2');});
console.log('script end');

输出顺序是: 
'script start' -> 'script end' -> 'promise1' -> 'promise2' -> 'setTimeout'

解释: 

1. 首先执行整个script(这是一个宏任务)

2. 遇到console.log('script start')直接执行

3. 遇到setTimeout, 回调加入宏任务队列

4. 遇到Promise.then, 第一个then回调加入微任务队列

5. 执行console.log('script end')

6. 当前宏任务执行完毕, 开始执行微任务队列

7. 执行第一个then回调, 输出'promise1', 然后又遇到第二个then, 其回调加入微任务队列

8. 继续执行微任务队列,输出'promise2'

9. 微任务队列清空,开始下一个宏任务(setTimeout回调),输出'setTimeout'

四、浏览器与Node环境差异1) 浏览器中的事件循环

浏览器的事件循环相对简单: 

  1. 执行宏任务

  2. 执行所有微任务

  3. 可能需要渲染

  4. 重复

2) Node.js中的事件循环

Node的事件循环分为多个阶段: 

  1. timers阶段: 执行setTimeout和setInterval回调

  2. I/O callbacks阶段: 执行几乎所有的回调(除了close事件、timers和setImmediate)

  3. idle, prepare阶段: 内部使用

  4. poll阶段: 检索新的I/O事件,执行相关回调

  5. check阶段: 执行setImmediate回调

  6. close callbacks阶段: 执行关闭事件的回调(如socket.on('close'))

在Node中, process.nextTick的优先级高于微任务Promise.then. 

来看个Node的例子: 

setTimeout(() => console.log('timeout'), 0);setImmediate(() => console.log('immediate'));
Promise.resolve().then(() => console.log('promise'));process.nextTick(() => console.log('nextTick'));

输出顺序可能是: 
'nextTick' -> 'promise' -> 'timeout' -> 'immediate'

解释: 

  1. process.nextTick优先级最高

  2. 然后是微任务Promise.then

  3. 然后是timers阶段的setTimeout

  4. 最后是check阶段的setImmediate