目录
JavaScript 执行机制与事件循环
一、同步与异步代码
1. 同步代码(Synchronous Code)
2. 异步代码(Asynchronous Code)
二、事件循环(Event Loop)
1. 核心组成
2. 事件循环基本流程
3. 运行机制
三、异步操作分类
1. 任务类型对比
2. 宏任务(Macrotasks)
3. 微任务(Microtasks)
4. 执行顺序规则
5. 代码执行顺序示例
6. 常见问题
四、Promise的同步异步问题
五、Node.js 与浏览器的事件循环差异
六、关键总结
示例
JavaScript 是单线程语言,但其通过 事件循环(Event Loop) 和 任务队列(Task Queue) 实现了非阻塞异步执行。
一、同步与异步代码
1. 同步代码(Synchronous Code)
-
特点:
-
顺序执行,阻塞后续代码
-
直接在主线程(调用栈)执行
-
典型场景:普通函数调用、数学运算
console.log('Start'); let sum = 0; for (let i = 0; i < 1e6; i++) sum += i; console.log('End'); // 必须等待循环执行完
-
2. 异步代码(Asynchronous Code)
-
特点:
-
非阻塞执行,后续代码无需等待
-
通过任务队列管理
-
典型场景:
setTimeout
、fetch
、Promise
、DOM事件
console.log('Start'); setTimeout(() => console.log('Timeout'), 0); console.log('End'); // 输出顺序:Start → End → Timeout
-
二、事件循环(Event Loop)
1. 核心组成
组件 | 作用 |
---|---|
调用栈 (Call Stack) | 存放同步执行代码(LIFO结构) |
任务队列 (Task Queue) | 存放待处理的异步任务 |
Web APIs | 浏览器提供的异步API(如DOM、定时器) |
2. 事件循环基本流程
事件循环是 JavaScript 处理异步代码的核心机制,其基本流程如下:
-
执行同步代码
先执行当前调用栈中的所有同步任务(如函数调用、变量赋值)。 -
处理微任务队列
同步代码执行完毕后,立即清空微任务队列(Microtask Queue)中的所有任务(如Promise.then
)。 -
渲染页面(如有需要)
执行 UI 渲染(布局、绘制),但浏览器会智能合并渲染操作以优化性能。 -
处理宏任务队列
从宏任务队列(Macrotask Queue)中取出一个任务执行,回到步骤 1 开始新的事件循环。
3. 运行机制
st=>start: 开始执行
op1=>operation: 执行同步代码
op2=>operation: 遇到异步任务
op3=>operation: 注册到Web APIs
op4=>operation: Web API完成,回调推入任务队列
cond=>condition: 调用栈是否为空?
op5=>operation: 取出队列首个任务推入调用栈
e=>end: 结束st->op1->op2->op3->op4->cond
cond(yes)->op5->cond
cond(no)->e
三、异步操作分类
1. 任务类型对比
特性 | 宏任务(Macrotask) | 微任务(Microtask) |
---|---|---|
常见类型 | setTimeout 、setInterval 、I/O操作、UI渲染 | Promise.then 、MutationObserver 、process.nextTick (Node.js) |
执行时机 | 每轮事件循环执行一个宏任务 | 当前宏任务执行完毕后立即执行所有微任务 |
优先级 | 低 | 高 |
2. 宏任务(Macrotasks)
-
常见类型:
-
setTimeout
/setInterval
-
I/O 操作(文件读写、网络请求)
-
UI 渲染(浏览器)
-
requestAnimationFrame
(浏览器)
-
-
特点:每次事件循环处理一个宏任务。
-
微任务优先级高于宏任务:每执行完一个宏任务后,会立即清空所有微任务。
3. 微任务(Microtasks)
-
常见类型:
-
Promise.then
/catch
/finally
-
MutationObserver
(浏览器) -
queueMicrotask
-
-
特点:在当前宏任务结束后、下一个宏任务开始前执行所有微任务。
-
微任务可嵌套:若在微任务中生成新的微任务,新微任务会在当前事件循环中被执行。
4. 执行顺序规则
-
执行当前宏任务中的同步代码
-
执行该宏任务产生的所有微任务
-
执行下一个宏任务
-
循环往复(每次循环称为一个"tick")
5. 代码执行顺序示例
console.log('Start');setTimeout(() => {console.log('Timeout');
}, 0);Promise.resolve().then(() => console.log('Promise 1')).then(() => console.log('Promise 2'));console.log('End');
输出顺序:
Start
End
Promise 1
Promise 2
Timeout
执行步骤:
-
同步代码依次执行,输出
Start
和End
。 -
setTimeout
回调进入宏任务队列。 -
Promise.then
回调进入微任务队列。 -
同步代码执行完毕,执行所有微任务(
Promise 1
、Promise 2
)。 -
执行下一个宏任务(
Timeout
)。
6. 常见问题
-
为什么微任务优先级高?
微任务通常用于更紧急的更新(如 Promise 状态变更),确保在渲染前完成数据更新,提升用户体验。 -
如何避免微任务饥饿?
避免在微任务中无限递归添加微任务,否则宏任务无法执行,导致页面卡死。 -
requestAnimationFrame
是宏任务还是微任务?
它属于渲染阶段的宏任务,用于在下次重绘前执行动画更新,优先级高于普通宏任务(如setTimeout
)。
四、Promise的同步异步问题
1. 同步执行:Promise 的构造函数和其执行器函数中的代码是同步执行的。
2. 异步回调:.then()
、.catch()
等回调函数是异步的微任务,会在当前同步代码执行完毕后执行。
3. 示例
console.log('1. 同步代码开始');const promise = new Promise((resolve) => {console.log('2. Promise 执行器函数(同步)');resolve('resolve 的值');
});promise.then((value) => {console.log('4. .then 回调(异步微任务):', value);
});console.log('3. 同步代码结束');// 输出顺序:
// 1. 同步代码开始
// 2. Promise 执行器函数(同步)
// 3. 同步代码结束
// 4. .then 回调(异步微任务): resolve 的值
4. 异步操作的嵌套
console.log('1. 同步代码开始');const promise = new Promise((resolve) => {console.log('2. Promise 执行器函数(同步)');setTimeout(() => {console.log('5. setTimeout 回调(异步宏任务)');resolve('resolve 的值');}, 0);
});promise.then((value) => {console.log('6. .then 回调(异步微任务):', value);
});console.log('3. 同步代码结束');// 输出顺序:
// 1. 同步代码开始
// 2. Promise 执行器函数(同步)
// 3. 同步代码结束
// 5. setTimeout 回调(异步宏任务)
// 6. .then 回调(异步微任务): resolve 的值
五、Node.js 与浏览器的事件循环差异
特性 | 浏览器 | Node.js |
---|---|---|
微任务类型 | Promise 、MutationObserver | Promise 、process.nextTick |
微任务优先级 | 同层级按注册顺序 | process.nextTick 优先级最高 |
宏任务分层 | 单层任务队列 | 多阶段分层(timers → pending → poll → check → close) |
六、关键总结
-
执行顺序铁律:
同步代码 → 微任务 → 宏任务 → 渲染(浏览器) -
微任务优先:
每个宏任务执行完毕后,必须清空所有微任务队列 -
任务嵌套规则:
-
微任务中产生的微任务会继续在当前批次执行
-
宏任务中产生的任务会进入下一轮循环
-
-
性能优化建议:
-
避免在微任务中进行耗时操作
-
合理分配任务类型(密集计算使用宏任务分片)
-
问答:
什么是事件循环?
答案:执行代码和收集异步任务,在调用栈空闲时,反复调用任务队列里回调函数执行机制。为什么有事件循环?
答案:JavaScript 是单线程的,为了不阻塞 JS 引擎,设计执行代码的模型。JavaScript 内代码如何执行?
答案:执行同步代码,遇到异步代码交给宿主浏览器环境执行 异步有了结果后,把回调函数放入任务队列排队 当调用栈空闲后,反复调用任务队列里的回调函数。什么是宏任务?
答案:浏览器执行的异步代码,例如:JS 执行脚本事件,setTimeout/setInterval,AJAX请求完成事件,用户交互事件等。什么是微任务?
答案:JS 引擎执行的异步代码,例如:Promise对象.then()的回调。
示例
回答下面代码执行顺序:
console.log(1)
setTimeout(() => {console.log(2)const p = new Promise(resolve => resolve(3))p.then(result => console.log(result))
}, 0)
const p = new Promise(resolve => {setTimeout(() => {console.log(4)}, 0)resolve(5)
})
p.then(result => console.log(result))
const p2 = new Promise(resolve => resolve(6))
p2.then(result => console.log(result))
console.log(7)
输出结果:
1
7
5
6
2
3
4