requestidlecallback与分时函数

前言

我们知道GUI 渲染线程与 JS 引擎线程是互斥的。当JS程序执行时间过长,会阻塞页面渲染和事件响应。那么针对一些优先级较低的任务,我们可以利用分时函数在主线程空闲时执行,减少卡顿。其本质是将一个长任务拆分成数个短任务,在其可执行时执行。

在这里插入图片描述

示例

这里我们使用官方的一个示例来说明:向页面中插入万级节点数据,可以看到,普通的循环插入卡顿感很明显(动画停止)。原因很简单:本该绘制的时候,因有任务要执行,所以浏览器渲染延后了。

在这里插入图片描述

之前在《Flip动画》一文中提到过“像素管道”这一概念:在每一个标准帧(约16.7ms)中,会执行JS代码、样式计算、布局、绘制等任务。假设某一帧里执行的任务较少,那么这一帧就有一定的空闲时间来执行requestIdleCallback回调。

requestIdleCallback

window.requestIdleCallback() 方法插入一个函数,这个函数将在浏览器空闲时期被调用。这使得开发者能够在主事件循环上执行后台和低优先级任务,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间timeout,则有可能为了在超时前执行函数而打乱执行顺序。

语法

requestIdleCallback(callback)
requestIdleCallback(callback, options)

参数

callback

一个在事件循环空闲时即将被调用的函数的引用。函数会接收到一个名为IdleDeadline的参数,这个参数可以获取当前空闲时间以及回调是否在超时时间前已经执行的状态。

options(可选)

如果指定了 timeout,并且有一个正值,而回调在 timeout 毫秒过后还没有被调用,那么回调任务将放入事件循环中排队,即使这样做有可能对性能产生负面影响。

返回值

一个ID,可以把它传入 window.cancelIdleCallback() 方法来结束回调。

充分利用空闲回调

因为 idle callback 旨在为代码提供一种与事件循环协作的方式,以确保充分利用系统资源,不会过度分配任务,从而导致延迟或其他性能问题。因此需要注意以下几点:

  • 对非高优先级的任务使用空闲回调

已经创建了多少回调,用户系统的繁忙程度,回调多久会执行一次(除非你指定了 timeout),这些都是未知的。不能保证每次事件循环后都能执行空闲回调;如果事件循环用尽了所有可用时间,那么回调将永远不会执行(除非设置 timeout)。

  • 空闲回调应尽可能不超支分配到的时间

尽管即使你超出了规定的时间上限,通常来说浏览器、代码、网页也能继续正常运行,这里的时间限制是用来保证系统能留有足够的时间去完成当前的事件循环然后进入下一个循环,而不会导致其他代码卡顿或动画效果延迟。目前,timeRemaining() 有一个 50 ms 的上限时间,但实际上你能用的时间比这个少,因为在复杂的页面中事件循环可能已经花费了其中的一部分,浏览器的扩展插件也需要处理时间(以防出现不可预测的任务, 避免无法及时响应使用户感知到延迟)。

  • 避免在空闲回调中改变 DOM

空闲回调执行的时候,当前帧已经结束绘制了,所有布局的更新和计算也已经完成。如果你做的改变影响了布局,你可能会强制停止浏览器并重新计算,这是不必要的。如果回调需要改变 DOM,应该使用 window.requestAnimationFrame() 来调度

  • 避免运行时间无法预测的任务

你的空闲回调必须避免做任何占用时间不可预测的事情。比如会影响页面布局的操作和执行Promise等,因为Promise的回调会在 idle 的回调执行完成后立即执行, 拉长当前帧的耗时。Promise的回调属于优先级较高的微任务,所以会在 requestIdleCallback 回调结束后立即执行,可能会给这一帧带来超时的风险。

  • 只在需要的时候才设置 timeout

使用 timeout 可以保证代码按时执行,但是在剩余时间不足以强制执行这部分代码,且保证浏览器性能的情况下,timeout 就会造成延迟或者动画不流畅。

注意点

在一些不支持此API的浏览器中,我们可以通过setTimeout()来达到近似的效果。当然,setTimeout() 并不能利用空闲时段,而是在情况允许时执行相关代码,从而尽可能避免造成性能问题或表现延迟。

// 定义
window.requestIdleCallback =window.requestIdleCallback ||function (handler) {let startTime = Date.now();return setTimeout(function () {handler({didTimeout: false,timeRemaining: function () {return Math.max(0, 50.0 - (Date.now() - startTime));},});}, 1);
};
// 取消
window.cancelIdleCallback =window.cancelIdleCallback ||function (id) {clearTimeout(id);
};

适用场景

以上内容为官方示例和相关解释,那么requestIdleCallback可以用于哪些实际场景中呢?

数据上报

记录用户操作行为并定时上报,如按钮点击、页面停留时长统计或异常快照图上传等。当处理数据量较大,或遇到网络异常需要中断恢复上报等情况时,可采用此API来调度上报时机,避免阻塞页面渲染(分时切片+IndexedDB是做数据上报的一个很好的解决方案)。

资源预加载

如一些资源不需要在系统初始化时加载,或者资源优先级较低,可以在后台慢慢加载。如微前端场景下,会优先加载当前激活的子应用的资源,而其它子应用相关资源就可以使用此API进行预加载(如qiankun的prefetch)。

检测卡顿

使用requestIdleCallback来执行记录,如果在一段时间内回调没执行的话,就可以认为是卡顿的。

如何封装一个分时函数

以第一个功能(循环插入DOM)为例,我们可以做如下实现

function performChunk(datas) {if (datas.length == 0) {return}let i = 0;function _run() {if (i == datas.length) {return}requestIdleCallback(idle => {while (idle.timeRemaining() > 0) {const item = datas[i];const div = document.createElement('div');div.textContent = item;document.body.appendChild(div);i++;}_run();})}_run();
}
// 执行
const datas = [// ...
]
performChunk(datas);

在这里插入图片描述

优化

  • 在一些不支持requestIdleCallback的浏览器或者其它环境中,需要兼容

  • 把业务逻辑与调度函数解耦

  • 调度函数兼容

// 定义 requestIdleCallback 的兼容处理,不执行则用setTimeout模拟实现
window.requestIdleCallback = window.requestIdleCallback || function (handler) {// 闭包,创建的时候记录一下时间let startTime = Date.now();return setTimeout(()=> {handler({didTimeout: false,timeRemaining: ()=> {// 理论上系统给你空闲的时间会低于50ms,所以你的任务最好不要超过50ms,否则还是会卡顿return Math.max(0, 50 - (Date.now() - startTime));}});}, 1);
};
// 取消任务
window.cancelIdleCallback = window.cancelIdleCallback || function (id) {clearTimeout(id);
};
// 定义功能逻辑
const consumer = (item) => {const div = document.createElement('div');div.textContent = item;document.body.appendChild(div);
}
// 定义自定义执行时机
const chunkSplitor = (task) => {setTimeout(()=>{task((time)=>time<16)},1000)
}
// 定义分时函数
function performChunk(datas, consumer, chunkSplitor) {// 此处可以做入参归一化处理if(Object.prototype.toString.call(datas) !== '[object Array]'){return}if (datas.length == 0) {return}// 默认执行时机if(!chunkSplitor){chunkSplitor = (task)=>{requestIdleCallback(idle=>{task(()=>idle.timeRemaining()>0)})}}let i = 0;function _run() {if (i == datas.length) {return}chunkSplitor((hasTime) => {// 每一帧还有剩余时间,则执行const now = Date.now();while (hasTime(Date.now() - now) && i < datas.length) {const item = datas[i];consumer(item)i++;}_run();})}_run();
}
// 执行
const datas = [// ...
]
performChunk(datas, consumer, chunkSplitor)

兼容性

在这里插入图片描述

后记

  • 在非及时场景需求中可以使用此API来避免计算和渲染线程冲突,增加响应流畅度
  • 此API无法在 Web Worker 中使用
  • 需要考虑API兼容性
  • 需要了解此API与requestAnimationFrame的区别和使用时机
  • 推荐去查阅React的Scheduler相关源码,看一下如何手动实现各个任务之间的调度

参考

MDN-requestIdleCallback
requestIdleCallback

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/55843.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

国产工具链GCKontrol-GCAir助力控制律开发快速验证

前言 随着航空领域技术的不断发展&#xff0c;飞机的飞行品质评估和优化成为了航空领域的一个重要任务&#xff0c;为了确保飞行器在各种复杂条件下的稳定性&#xff0c;控制律设计过程中的模型和数据验证需要大量仿真和测试。 本文将探讨基于世冠科技的国产软件工具链GCKont…

Leetcode 37. 解数独

1.题目基本信息 1.1.题目描述 编写一个程序&#xff0c;通过填充空格来解决数独问题。 数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 33 宫内只能出现一次。&#xff08;请参考…

如何设置 GitLab 密码长度?

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料&#xff1a; 极狐GitLab 60天专业…

cudnn8编译caffe过程(保姆级图文全过程,涵盖各种报错及解决办法)

众所周知,caffe是个较老的框架,而且只支持到cudnn7,但是笔者在复现ds-slam过程中又必须编译caffe,我的cuda版本是11.4,最低只支持到8.2.4,故没办法,只能编译了 在此记录过程、报错及解决办法如下; 首先安装依赖: sudo apt-get install git sudo apt-get install lib…

facebook受众选择设置策略的最佳方式

在进行Facebookguanggao投放时&#xff0c;受众的选择是一个至关重要的步骤。正确的受众选择不仅能够帮助我们更好地定位目标用户&#xff0c;还能显著提高guanggao的转化率和投资回报率&#xff08;ROI&#xff09;。然而&#xff0c;受众选择的数量和范围同样是需要认真考虑的…

【Tor】使用Debian系统搭建obfs4 Bridge网桥

你好 我是无聊的木子。 目录 前言 写作の原因 网桥是个啥? 正文 - 到底咋搭建捏 搞台机子先 比较简便の方法 - 买台云服务器 首月五折 一元试用 远程连接服务器 更加复杂の办法 - 自己拿物理机做网桥 开始搭建网桥 先安装Tor 然后配置网桥 最后组合网桥 找到fin…

大数据面试-笔试SQL

一个表table: c_id u_id score&#xff1b;用SQL计算每个班级top5学生的平均分&#xff08;腾讯&#xff09; select class_id,avg(score) as score_avg from (select *,row_number() over(partition by class_id order by score desc) as score_rank from table ) t1 where t…

AI推理部署工具之大汇总,后面会逐步补充

目录 1、FastDeploy 1.1 安装 1.2 yolo推理部署示例 1.3 推理部署思路 1、FastDeploy FastDeploy 通过提供简洁的API接口&#xff0c;让AI推理部署变得更加高效和灵活。适用于多种主流算法模型&#xff0c;且支持跨平台、多硬件兼容等优势。 支持 GPU、CPU、Jetson、ARM …

研发中台拆分之路:深度剖析、心得总结与经验分享

背景在 21 年&#xff0c;中台拆分在 21 年&#xff0c;以下为中台拆分的过程心得&#xff0c;带有一定的主观&#xff0c;偏向于中小团队中台建设参考&#xff08;这里的中小团队指 3-100 人的团队&#xff09;&#xff0c;对于大型团队不太适用&#xff0c;毕竟大型团队人中 …

Qt源码-Qt多媒体音频框架

Qt 多媒体音频框架 一、概述二、音频设计1. ALSA 基础2. Qt 音频类1. 接口实现2. alsa 插件实现 一、概述 环境详细Qt版本Qt 5.15操作系统Deepin v23代码工具Visual Code源码https://github.com/qt/qtmultimedia/tree/5.15 这里记录一下在Linux下Qt 的 Qt Multimedia 模块的设…

Java | Leetcode Java题解之第472题连接词

题目&#xff1a; 题解&#xff1a; class Solution {Trie trie new Trie();public List<String> findAllConcatenatedWordsInADict(String[] words) {List<String> ans new ArrayList<String>();Arrays.sort(words, (a, b) -> a.length() - b.length(…

RelationGraph实现工单进度图——js技能提升

直接上图&#xff1a; 从上图中可以看到整个工单的进度是从【开始】指向【PCB判责】【完善客诉】【PCBA列表】&#xff0c;同时【完善客诉】又可以同时指向【PCB判责】【PCBA列表】&#xff0c;后续各自指向自己的进度。 直接上代码&#xff1a; 1.安装 1.1 Npm 方式 npm …

JavaScript下载文件(简单模式、跨域问题、文件压缩)

文章目录 简介简单文件下载通过模拟form表单提交通过XMLHttpRequest方式 跨域(oss)下载并压缩文件完整示例文件压缩跨域设置 简介 相信各位开发朋友都遇到过下载的文件的需求&#xff0c;有的非常简单&#xff0c;基本链接的形式就可以。 有的就比较复杂&#xff0c;涉及跨域…

【顶刊核心变量】中国地级市绿色金融试点改革试验区名单数据(2010-2023年)

一、测算方式&#xff1a; 参考《中国工业经济》崔惠玉&#xff08;2023&#xff09;老师的研究&#xff0c;2017 年&#xff0c;国务院决定将浙江、广东、江西、贵州和新疆的部分地区作为绿色金融改革创新试验 区的首批试点地区。试点地区在顶层设计、组织体系、产品创新、配…

Biomamba求职| 国奖+4篇一作SCI

转眼间我也要参加秋招啦&#xff0c;认真的求职帖&#xff0c;各位老师/老板欢迎联系~其它需要求职的小伙伴也欢迎把简历发给我们&#xff0c;大家一起找工作。 一、基本信息 姓名&#xff1a;Biomamba 性别&#xff1a;男 出厂年份&#xff1a;1998 籍贯&#xff1a;浙江…

flutter升级,从3.10.6升级到3.16.9 混编项目iOS跑不起来

flutter升级&#xff0c;从3.10.6升级到3.16.9&#xff0c;如果直接去终端用命令行flutter upgrade v3.16.9很难保证不进入 dev分支升级成beta版本。 所以采取了 https://docs.flutter.dev/release/archive 点击这里去进行升级&#xff0c;这个时候也不要直接替换&#xff0c…

Visual Studio 2022安装(含重生版)

前言&#xff1a; 昨天调试代码的时候发现程序怎么都运行不了&#xff0c;错误显示无法找到文件啊啊啊&#xff0c;能力有限&#xff0c;找不出错误源&#xff0c;然后就狠心删掉所有相关文件来“重新开始”&#xff01; 正文&#xff1a; 1.官网下载&#xff08;内定中文版…

GS-SLAM论文阅读笔记-CG-SLAM

前言 这是一篇不是最新的工作&#xff0c;我之前没有阅读&#xff0c;但是我前几天阅读GLC-SLAM的时候&#xff0c;发现它的一部分内容参考了CG-SLAM&#xff0c;并且CG-SLAM最近被ECCV2024接收&#xff0c;说明这是一片值得参考的好文章&#xff0c;接下来就阅读一下吧&#…

QUUID 使用详解

UUID 通常由 128 位&#xff08;16 字节&#xff09;组成&#xff0c;通常表示为 32 个十六进制数字&#xff0c;分为五个部分&#xff0c;格式如下&#xff1a; QUuid 是 Qt 框架中用于生成和处理 UUID&#xff08;通用唯一标识符&#xff09;的类。UUID 是一种标准的标识符格…

sklearn机器学习实战——随机森林回归与特征重要性分析全过程(附完整代码和结果图)

sklearn机器学习实战——随机森林回归与特征重要性分析全过程&#xff08;附完整代码和结果图&#xff09; 关于作者 作者&#xff1a;小白熊 作者简介&#xff1a;精通python、matlab、c#语言&#xff0c;擅长机器学习&#xff0c;深度学习&#xff0c;机器视觉&#xff0c;目…