引言
你是否好奇过这段代码的执行顺序?为什么Promise总是比setTimeout先执行?让我们通过实际代码来揭开JavaScript事件循环的神秘面纱。
什么是Event Loop(事件循环)
**Event Loop(事件循环)**是JavaScript的执行机制,也是代码执行的开始。它是JavaScript引擎处理异步操作的核心机制,解决了单线程语言如何处理并发任务的问题。
Event Loop的本质
事件循环机制让JavaScript能够:
- 非阻塞执行:处理耗时操作而不冻结用户界面
- 任务调度:合理安排同步和异步任务的执行顺序
- 性能优化:通过任务优先级实现高效的资源利用
Event Loop的工作原理

关键特点:
- 循环执行:事件循环会不断重复这个过程
- 阶段性处理:每个阶段处理特定类型的任务
- 微任务优先:每个阶段结束后都会清空微任务队列
核心概念:单线程的智慧
JavaScript为什么是单线程?
javascript
// 一个 script 就是一个宏任务开始
// 同步任务
// js 调用栈
// cpu 计算
正如注释所说,JavaScript采用单线程设计有其深刻原因:
- DOM操作安全:避免多线程同时修改DOM造成冲突
- 简化编程模型:无需考虑线程同步和锁机制
- 保证执行顺序:代码按预期顺序执行
单线程的执行策略
同一时刻只做一件事,但JavaScript通过巧妙的任务调度实现了高效执行:
同步任务优先级:
- 同步任务要尽快执行完
- 渲染页面(重绘重排)
- 响应用户的交互(优先)
耗时性任务的处理:
setTimeout/setInterval
- 定时器任务fetch/ajax
- 网络请求eventListener
- 事件监听
这些耗时任务被放入任务队列,避免阻塞主线程。
实战解析一:基础事件循环
让我们分析第一个核心示例:
javascript
// 同步任务
console.log('script start');
// 异步任务,宏任务 任务队列
setTimeout(()=>{
console.log('setTimeout');
},0)
// .then 异步 微任务
// static 静态方法
Promise.resolve().then(()=>{
console.log('promise');
})
// 同步任务
console.log('script end');
执行结果分析
输出顺序:
arduino
script start
script end
promise
setTimeout
为什么是这个顺序?
- 同步代码优先 :
script start
和script end
立即执行 - 微任务抢跑 :
Promise.then()
是微任务,优先级高于宏任务 - 宏任务排队 :
setTimeout
是宏任务,最后执行
关键理解点
注释中提到的"异步任务 耗时性的任务 任务放到哪个地方?",答案是:
宏任务队列(script脚本就是宏任务):
setTimeout/setInterval
- 定时器fetch/ajax
- 网络请求eventListener
- 事件监听- I/O操作
微任务队列(紧急的,优先的,同步任务执行完后的补充):
Promise.then()
- Promise回调MutationObserver
- DOM变化监听queueMicrotask
- 手动微任务process.nextTick()
- Node.js专用
⚠️ 重要注意 :Promise构造函数本身是同步执行的,只有Promise的
.then()
、.catch()
、.finally()
回调才是微任务!
实战解析二:多个Promise的执行
javascript
console.log('script start');
const promise1 = Promise.resolve('First Promise');
const promise2 = Promise.resolve('Second Promise');
const promise3 = new Promise(resolve => {
resolve('Third Promise');
})
promise1.then(value => console.log(value));
promise2.then(value => console.log(value));
promise3.then(value => console.log(value));
setTimeout(()=>{
console.log('下一把再相见');
},0)
console.log('同步end')
执行结果分析
输出顺序:
sql
script start
同步end
First Promise
Second Promise
Third Promise
下一把再相见
深度理解:
- 所有Promise.then都是微任务,会在当前宏任务结束后批量执行
- 微任务队列会完全清空后才执行下一个宏任务
- 这就是为什么所有Promise都在setTimeout之前执行
实战解析三:Promise构造函数的陷阱
javascript
var p4 = new Promise((resolve,reject)=>{
// 宏任务
// 先执行
setTimeout(()=>{
resolve(1000)// 将第一个 .then 的回调 放到微任务队列中
console.log('哈哈哈')
},0)
})
p4.then((res)=>{
// 执行第一个.then 的回调
console.log('1')// 1
console.log(res)// 1000
console.log('2')// 2
}).then(()=>{
console.log('第二次then')
})
执行结果分析
输出顺序:
yaml
哈哈哈
1
1000
2
第二次then
关键理解:
- Promise构造函数同步执行:setTimeout立即被调度
- resolve触发时机:只有当setTimeout执行时,resolve才被调用
- then链式执行:第一个then执行完后,立即执行第二个then
注释"将第一个 .then 的回调 放到微任务队列中"精准地说明了resolve的作用机制。
实战解析四:Node.js环境的特殊性
javascript
console.log('Start');
// node 微任务
// process 进程对象
process.nextTick(() => {
console.log('Process Next Tick');
})
// 微任务
Promise.resolve().then(() => {
console.log('Promise Resolved');
})
// 宏任务
setTimeout(() => {
console.log('haha');
Promise.resolve().then(() => {
console.log('inner Promise')
})
}, 0)
console.log('end');
执行结果分析
Node.js输出顺序:
sql
Start
end
Process Next Tick
Promise Resolved
haha
inner Promise
Node.js特殊规则:
process.nextTick
优先级最高,甚至高于Promise.then- 这是Node.js独有的微任务,浏览器环境没有
实战解析五:DOM操作与微任务
javascript
// event loop 是JS 执行机制,也是代码执行的开始
// html 是第一个BFC 块级格式化上下文
const target = document.createElement('div');
document.body.appendChild(target);
const observer = new MutationObserver(() => {
console.log('微任务: MutationObserver');
})
// 监听target 节点的变化
observer.observe(target, {
attributes: true,
childList: true,
})
target.setAttribute('data-set', '123');
target.appendChild(document.createElement('span'));
target.setAttribute('style', 'background-color: green;');
执行结果分析
输出:
makefile
微任务: MutationObserver
深度理解:
- MutationObserver会批量监听DOM变化
- 多次DOM操作只触发一次回调
- 在页面渲染前执行,性能优化的关键
注释"DOM 改变在页面渲染前 拿到DOM 有啥改变"完美解释了MutationObserver的价值:
- 时机优势:在DOM更新后、页面渲染前执行
- 性能优化:避免多次重绘重排
- 批量处理:一次性获取所有DOM变化信息
- 应用场景:组件库的响应式更新、性能监控
实战解析六:queueMicrotask的妙用
javascript
console.log('script start');
// 批量更新
// dom树 cssom layout 树 图层合并
queueMicrotask(()=>{
// DOM 更新了,但不是渲染完了
// 一个元素的高度 offsetHeight scrollTop
// 立即重绘重排 耗性能
console.log('微任务:queueMicrotask');
})
console.log('script end');
执行结果分析
输出顺序:
ruby
script start
script end
微任务:queueMicrotask
性能优化关键:
- 在DOM更新后、页面渲染前执行
- 避免"立即重绘重排 耗性能"的问题
- 批量处理DOM操作的最佳时机
核心规律总结
基于所有代码示例的分析,事件循环遵循以下铁律:
执行优先级
- 同步代码 - 立即执行
- 微任务队列 - 批量清空
- 宏任务队列 - 逐个执行
微任务优先级(Node.js)
process.nextTick
- 最高优先级Promise.then
- 标准微任务MutationObserver
- DOM相关微任务queueMicrotask
- 手动微任务
实际应用价值
性能优化:
- 使用微任务进行DOM批量更新
- 避免不必要的重绘重排
- 合理调度异步任务
代码调试:
- 理解异步代码的执行时机
- 预测代码输出顺序
- 解决时序相关的bug
结语
通过对这些实际代码的深度解析,我们不仅理解了事件循环的执行机制,更重要的是掌握了为什么会产生这样的执行结果。每一行注释都蕴含着深刻的理解,每一个输出顺序都有其必然的逻辑。
掌握事件循环,就是掌握了JavaScript异步编程的精髓。在实际开发中,这些知识将帮助你写出更高效、更可预测的代码。