60 秒回答模板

浏览器的 JS 执行和渲染大多共享主线程。一轮事件循环通常是取一个 task 执行,例如 script、setTimeout、点击回调;task 结束后清空 microtask 队列,例如 Promise.then、queueMicrotask;之后浏览器如果需要且时机合适,会执行 requestAnimationFrame 回调,并进行样式计算、布局、绘制和合成。渲染不是每轮循环必然发生,但只有主线程空出来才有机会渲染。长 JS 任务会阻塞用户输入和绘制,大量递归微任务也会让页面迟迟没有渲染机会。优化上要拆分长任务、减少同步布局读写、把重计算放到 Worker,并用 requestAnimationFrame 安排视觉更新。

考点 核心机制与工程取舍
难度 中高频面试题
回答目标 按定义、机制、场景讲清楚

深入解析

01

先说明主线程约束

JS 执行、样式计算、布局和绘制很多阶段都依赖主线程。只要 JS 长时间占用主线程,浏览器就无法及时处理输入和更新画面。

02

一轮循环的关键顺序

浏览器先执行一个宏任务,宏任务结束后清空所有微任务。微任务清空后,浏览器才有机会进入渲染步骤。这个顺序解释了 Promise.then 通常早于 setTimeout 后续任务,也解释了微任务过多会饿住渲染。

03

渲染机会不是每轮必有

浏览器会结合刷新率、页面可见性、是否有样式变化等因素决定是否渲染。没有视觉变化或时机未到时,可能跳过渲染;但主线程被占满时,必要渲染也会被推迟。

04

requestAnimationFrame 的位置

rAF 回调会在下一次绘制前执行,适合读取上一帧状态并提交本帧视觉更新。setTimeout 不和帧严格对齐,做动画可能抖动。

05

长任务和微任务的风险

一个耗时 200ms 的同步循环会直接阻塞多帧;不断在 Promise.then 里继续 queueMicrotask 也会让微任务队列长期不空,渲染机会被延后。

06

性能优化要围绕帧预算

把大任务切片到多个 task,视觉更新放 rAF,低优先级工作放 requestIdleCallback,CPU 密集计算放 Worker,并避免在同一帧里反复读写布局造成强制同步布局。

易错点

  • 说每执行完一个宏任务浏览器一定渲染一次,忽略渲染机会受刷新率、可见性和是否需要更新影响。
  • 认为微任务越快越安全,忽略微任务队列会一次性清空,递归微任务会推迟渲染。
  • 把 rAF 当成普通定时器,答不出它和绘制前时机的关系。
  • 只背宏任务和微任务顺序,不联系长任务、布局计算和用户看到的卡顿。

面试官追问

微任务会在渲染前执行吗?

通常一个宏任务结束后会先清空微任务队列,然后浏览器才有机会渲染。因此大量微任务可能推迟渲染。

requestAnimationFrame 为什么适合动画?

它在浏览器下一次绘制前回调,和刷新节奏对齐,适合集中做本帧视觉更新,减少 setTimeout 动画的抖动。

setTimeout(fn, 0) 为什么不能立刻执行?

它会进入任务队列,必须等当前任务、当前任务产生的微任务,以及前面排队的任务处理后才轮到它。

长列表渲染卡顿怎么从事件循环角度优化?

减少一次性同步工作,分片渲染或虚拟列表;把计算移到 Worker;DOM 读写分离,并把视觉提交放到 rAF。