使用isInputPending()来改进JavaScript调度效率

3,049 阅读3分钟

这是我参与8月更文挑战的第15天,活动详情查看:8月更文挑战

前言

isInputPending()FackBook与Google合作在Chrome浏览器上加入的一个Scheduling API,也是第一个将中断这个操作系统概念用于网页开发的API,开发者可以使用这个API来平衡JS执行、页面渲染及用户输入之间的优先级,就像系统使用中断调度CPU处理IO输入一样。

介绍

目前,isInputPending作为WICG的一个孵化标准,官方文档:Early detection of input events (wicg.github.io),其在Chrome 87 以上版本完全可用, 实测相同内核版本的Edge也没问题。

image.png

这个API是脸书为了权衡用户输入响应以及页面加载、脚本执行效率而制定的。当浏览器有需要处理的输入事件时,调用isInputPending()会返回true,在不传入任何参数的情况下,将会检测所有类型的输入事件,包括按键、鼠标、滚轮触控等DOM UI事件,也可以手动传入一个包含事件类型的数组参数。

浏览器特性检测:

if(navigator.scheduling.isInputPending){
   // TODO
}

诞生背景

众所周知,JavaScript是一门单线程的语言,浏览器使用异步非阻塞的事件循环模型来调度JS任务。在任务执行的过程中,大多数任务都是在主线程中完成,对于UI渲染和脚本操作这两种互斥操作来说,单线程无疑是非常安全的。但主线程往往还要处理用户交互,如果脚本执行时间过长,则会导致无法及时处理用户响应,导致页面卡顿,用户体验降低。

以往我们将一些计算密集型或高延迟的任务放到Web Worker中执行,执行完毕后再通知主线程获取结果,但是Web Worker中不能操作dom,而动画绘制等一些UI操作也是一个非常耗时的操作,我们只能在主线程中处理。

目前对于JS调度的最佳实践是将需要长时间执行的JS任务分成一块一块,然后分别放入队列中依次执行,每次执行完后将控制权交还给浏览器,这时浏览器再看事件队列是否有需要响应的事件,全部响应完毕后再次执行JS任务,这类似于操作系统作业调度中的时间片轮转算法,每个任务都能公平得到处理。

image.png

不过,从上图看到,加载快和响应快是不可兼得的,如果JS执行时间太长,那么浏览器处理事件响应的的时机也会延迟:如果将JS任务分成一块一块执行以此来提高响应速度,那么页面的加载时间就会变长。

示例

检查任何输入事件

while (workQueue.length > 0) {
  if (navigator.scheduling.isInputPending()) {
    break;
  }
  let job = workQueue.shift();
  job.execute();
}

检查特定输入事件

while (workQueue.length > 0) {
  if (navigator.scheduling.isInputPending(['mousedown', 'mouseup', 'keydown', 'keyup'])) {
    break;
  }
  let job = workQueue.shift();
  job.execute();
}

总结

通过合理使用isInputPending方法,我们可以在页面渲染时及时响应用户输入,并且,当有长耗时的JS任务要执行时,可以通过isInputPending来中断JS的执行,将控制权交还给浏览器来执行用户响应。

参考