什么是 INP
今天听组内同学分享,了解到一个前端概念 INP(Interaction to Next Paint),它用来衡量互动性,为了提供良好的用户体验,网页的 INP 应不超过 200 毫秒。
INP 指标通过观测网页在用户访问期间发生的所有点击、点按和键盘互动的延迟时间,评估网页对用户互动的总体响应情况。最终 INP 值是观测到的最长互动时间,离群值会被忽略。
常见的 INP 优化手段
INP 的通常优化手段是将大任务拆解成小任务,然后通过 setTimeout 调用,保障用户输入的响应。比如一个 200ms 的任务,将它拆分成 4 个 50ms 的任务来执行。
1 | // 原来的重任务 |
疑问
到这里移动端的同学是不是突然有些疑问
- JavaScript 是单线程的,拆分成小任务,并不能缩短整体的执行时间,这个优化有用吗
- 每个任务 50ms,它执行完成之后,下一个任务又会被执行,这中间的间隙能响应用户的操作吗
- 两个任务中间的窗口期这么短,会有用户命中吗
解答
问题 1
JavaScript 引擎是单线程,拆分成小任务,并不能缩短时长,甚至因为调度的问题,执行时长还会增加,但这类优化的目标不是“加速”,而是让用户的操作在执行任务的间隙能更快被响应。
问题 2
JavaScript 的 Event Loop 循环,执行完任务会执行其他的,这时候可以执行用户的操作,如果这个用户的操作耗时也比较长,整体 Loop 的执行也会加长,导致下一个 50ms 的任务更靠后执行
问题 3
会的,在这 50ms 执行期间,用户的操作仍能被接收,但是不会被处理,而是延后处理,所以这个优化会保证用户的操作尽快响应,减少卡顿感。
对于在第一个 50ms 用户做的操作,
- 假如没有优化,要到 200ms 之后才会执行
- 假如进行了优化,会在第一个 50ms 任务执行完成后执行,因为这个时候进入了下一个 Event Loop 周期,可以处理积压的用户操作了
新的问题 4 - 为什么使用定时器就可以保证每个任务之间有间隔
因为浏览器的定时器是一个单独的线程,setTimeout(task1, 0) 的任务并不会立即执行,而是把任务放入宏任务队列,等主线程空闲时才取出来执行。
浏览器规定,同一宏任务结束后,主线程会先处理 微任务队列(Promise、MutationObserver),再去处理下一个宏任务(下一个 setTimeout)。所以即使设置 0ms,也会有一个事件循环的调度间隙,因为主线程在任务之间会检查是否有用户事件或渲染需要处理。
新的问题 5 - 一次性加进去了 4 个定时任务,不会一次性全部取出执行吗
setTimeout 任务是宏任务,每次事件循环只取一个宏任务执行,即使宏任务队列里有 4 个 setTimeout,也不会一次性全部执行。事件循环每次只取队列头部的一个任务执行。
task1 执行完后,浏览器会:
- 处理微任务队列(Promise.then 等)
- 更新渲染(如果需要)
- 处理用户事件(点击/输入)
- 然后才执行 task2,依次类推
扩展 - 浏览器的其他线程
- 主线程:执行 JS、解析 HTML、构建 DOM 树、计算样式、布局、绘制。几乎所有前端逻辑都在这里执行。
- 合成线程:负责将多个图层合成,决定哪些区域需要重绘。加速滚动与动画渲染,使页面更流畅。
- Worker 线程:运行在后台的 JS 线程,处理计算密集或网络请求任务,不阻塞主线程。
- 定时器线程:负责 setTimeout / setInterval 的计时实现。
- 事件线:管理用户输入事件(鼠标、键盘、触摸),并将其派发给主线程。
- 动画线程: 用于协调 requestAnimationFrame 与浏览器渲染节奏。
扩展 - 和移动端的区别
- 移动端是利用多线程,把耗时操作丢到其他线程处理,保证主线程的流畅度
- 前端也有类似的操作,如使用 Web Worker
扩展 - 为什么是 50ms
这个数字并不是固定标准,而是经验值,因为浏览器通常以 60 帧每秒(16.7ms 每帧)刷新,如果一个任务执行时间太长,就会阻塞渲染导致掉帧,将任务控制在 50ms 以内,能有效保证交互和动画的流畅性。
扩展 - 其他优化手段
除了拆分任务外,INP 也还有其他的一些优化手段,比如说把复杂耗时的计算丢到 Web Worker 当中去处理,这是一个单独的线程,不会阻塞 JavaScript 引擎线程。
其他还有一些更细节的优化,大家也可以自己去搜索,此处就不再赘述了。
参考文章: