你点了个按钮,页面愣了一拍。加载动画没出来,整个界面像被冻住,等反应过来所有东西一起刷新。这种微妙的卡顿,正好是Interaction to Next Paint(INP)要衡量的。罪魁祸首通常是长任务——一段JavaScript霸占着主线程不放,浏览器没法绘制新帧,也没法响应你的输入,非得等它跑完才行。
老办法是拿setTimeout把大块任务切碎,每处理一小段就让出控制权,浏览器趁机喘口气,处理积压的用户操作。这招确实管用,但有个隐藏成本。scheduler.yield()能干同样的活,还没有那个成本,而且现在主流浏览器基本都支持了。如果你一直没当回事,这篇东西值得看看。
为什么setTimeout让出控制权会吃亏?传统的写法是这样:你要处理一个大数组,所以隔一阵让出一次控制权。把长任务拆成短任务,对降低输入延迟确实有好处。问题出在你的后续代码被塞到了哪里。用setTimeout让出时,你剩下的函数逻辑会排到任务队列末尾。在此期间,任何其他被调度进来的东西——第三方统计回调、别的组件任务、另一个setTimeout——全都会先拿到控制权。你的循环可能被一堆你根本不关心的任务堵在后面,一个本来100毫秒能干完的活,完成时间变得完全不可预测。你本意是礼貌让行,结果浏览器当真了,把你的代码晾在了一边。
scheduler.yield()的做法有什么不同?它返回一个可以await的promise。执行到那里就暂停,把主线程交还给浏览器,这一点跟setTimeout那招完全一样,所以积压的交互可以及时处理。区别在于,你后续的代码会被放进一个优先级更高的队列里。等浏览器回过头来继续执行时,你的函数会排在其他同类等待任务的前面恢复执行,而不是被挤到后面。
改写循环的代码简单得不像话,结构不变,行为更好。你仍然让高优先级的交互工作插队,这正是让出控制权的本意,但你自己的后续逻辑不会被推到无界队列的最末尾。按照Chrome的设计框架,scheduler.yield()产生的后续任务,其优先级高于同等优先级的scheduler.postTask()任务,你的循环就不会被饿死。一个常见的实际场景是事件处理器需要先给出视觉反馈,再干重活:先把加载动画显示出来,调一次scheduler.yield()让浏览器把动画画出来,然后再执行耗时的内容替换操作。如果不用yield,显示动画和内容替换会合成一个长任务,动画根本来不及显示。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.