775 字
4 分钟
JavaScript 同步和异步
.png)
核心定义
JavaScript 是单线程语言,通过事件循环机制处理异步操作:
- 同步代码:顺序执行,阻塞后续任务
- 异步代码:
- 非阻塞操作(I/O、定时器等)
- 完成后将回调推入任务队列
- 事件循环:
- 主线程执行栈清空后
- 检查并执行任务队列中的回调 包括宏任务(script/setTimeout)和微任务(Promise/MutationObserver)
工作原理
- 执行同步代码:顺序执行主线程任务
- 注册异步任务:遇到异步操作(如
setTimeout
)交给宿主环境处理 - 事件循环:
- 主线程空闲时检查任务队列
- 优先清空微任务队列
- 执行一个宏任务
- 重复循环
- 回调执行:异步完成后回调加入队列,由事件循环调度执行
flowchart TD A[执行主线程同步代码] --> B{遇到异步操作} B -->|是| C[宿主环境处理异步] B -->|否| D[继续执行] C -->|完成| E[回调加入任务队列] D --> F{主线程空闲?} F -->|是| G[事件循环检查队列] G --> H{有微任务?} H -->|是| I[执行所有微任务] H -->|否| J[执行一个宏任务] I & J --> K[更新渲染] K --> G
关键点
- 单线程模型:JS 引擎单线程执行,异步靠宿主环境多线程支持
- 任务队列:
- 宏任务队列:script/setTimeout/setInterval/UI渲染
- 微任务队列:Promise.then/MutationObserver/process.nextTick
- 优先级:微任务 > 宏任务
- 异步方式:回调 → Promise → async/await
- 并发控制:Web Workers 实现多线程
- 渲染时机:事件循环每次迭代后可能更新渲染
常见误区
setTimeout(fn,0)
误解:实际延迟至少4ms(HTML5规范)- 阻塞事件循环:同步代码过长导致页面卡死
- 回调地狱:嵌套回调导致代码难以维护
- 微任务优先级误判:认为微任务在宏任务后执行
- 忽略任务来源:不同任务队列执行顺序混淆
应用场景
场景 | 异步方案 | 优势 |
---|---|---|
网络请求 | fetch().then() / async-await | 非阻塞UI渲染 |
定时任务 | setTimeout /setInterval | 延迟或周期性操作 |
用户交互响应 | addEventListener | 事件驱动不阻塞主线程 |
批量DOM操作 | MutationObserver | 微任务中高效处理DOM变化 |
CPU密集型任务 | Web Workers | 多线程并行计算 |
动画优化 | requestAnimationFrame | 与渲染时机同步 |
关联知识
- Promise:
- 三种状态:pending/fulfilled/rejected
- 链式调用:
.then().catch().finally()
- async/await:
- 语法糖:async函数返回Promise
- await暂停执行直到Promise完成
- 事件循环阶段:
- 执行同步代码
- 执行所有微任务
- 执行一个宏任务
- 重复2-3
- Web API:
- 浏览器提供的异步能力(DOM事件/XMLHttpRequest/File API)
- 错误处理:
- 同步:
try/catch
- 异步:Promise.catch/async的try-catch
- 同步:
- 并发模型:
- Web Workers(独立线程)
- SharedArrayBuffer(共享内存)
- 性能优化:
- 任务拆分(setTimeout分片)
- 空闲调度(requestIdleCallback)
💡 黄金法则:
- 同步代码 → 所有微任务 → 一个宏任务 → 重复
- 微任务优先级最高(Promise.then)
- 避免长任务阻塞渲染(>50ms) 使用
performance.now()
和 Chrome DevTools Performance 面板分析任务时序!