数据环境
- 高频率:WebSocket按毫秒级接收数据(高频时达1毫秒接收七八条)
- 高性能消耗:接收到的每一条数据都需要进行计算处理
- 中量数据:需要渲染的数据量很大
- 要求:实时更新
如果不做优化,会造成浏览器渲染卡顿,甚至浏览器卡死
如果你遇到和我相似的业务,不防花刷两个视频的时间看看本文内容,写者为小白, 望大佬指教
webWorker优化
思路:把数据接收和数据处理的部分放到单独的线程执行,较简单不是主角就不细说了
处理好数据后向主线程推送数据
这里可以有一个降速度处理(建议):
//我的思路:将接收到的数据临时存储后(去重),按照16毫秒(60帧)的速度推送到主线程,减少主线程压力(在数据量很大的适合建议实现,我以下例子未使用)
来自webWorker的消息处理
这里先贴一下webWorker信息处理的部分,方便后面理解,就是接收,秒过哈
//#主线程
//来自Worker的消息处理
const onmessageFn = (e: any) => {
const res = e.data
const { code, data } = res//我将webWorker来的数据进行过处理,所以需要解构
switch (code) {
case 'data'://单条数据
/**
* 执行体 高频率单条数据更新(渲染)
*/
break;
case 'list'://高量数据
/**
* 执行体 低频高性能消耗渲染列表更新(渲染)
*/
break;
default:
break;
}
}
帧优化(主角)
在主线程执行
增加初步空闲帧优化
概念引入
requestIdleCallback(fn)简单理解就是当浏览器有渲染帧不用做事情空闲出来时,就会执行传入的方法,后面就叫他空闲帧了
(接口参考)
优化思路:
//来自Worker的消息处理
const onmessageFn = (e: any) => {
const res = e.data
const { code, data } = res
switch (code) {
case 'data':
requestIdleCallback(() => {//空闲帧执行---------------------------这里
/**
* 执行体 高频率单条数据更新(渲染)
*/
}, { timeout: 500 })//500毫秒依然存活且不备执行,则放入js同步任务队列
break;
case 'list':
requestIdleCallback(() => {//空闲帧执行---------------------------这里
/**
* 执行体 低频高性能消耗渲染列表更新(渲染)
*/
})
break;
default:
break;
}
}
- 这时页面已经不再卡顿,因为只有在上一个渲染完成后,再执行
- 衍生新问题:执行阻塞,不能达到实时效果
引入虚拟队列
思路:拿一个数组把渲染实时单条数据空闲帧记录下来,方便后面关闭堵塞的空闲帧
(主要没有找到直接操作js任务队列的api,不然直接清空任务队列)
requestIdleCallback()返回一个一个 ID,可以把它传入
Window.cancelIdleCallback()方法来结束回调
保存返回的ID
//主要代码
callBackArr.push(requestIdleCallback(() => {}, { timeout: 500 }))
let callBackArr: Array<any> = []//空闲帧ID数组 ---虚拟队列(叫着顺口)
//来自Worker的消息处理
const onmessageFn = (e: any) => {
const res = e.data
const { code, data } = res
switch (code) {
case 'data':
callBackArr.push(requestIdleCallback(() => {//空闲帧执行-------------这里
/**
* 执行体 高频率单条数据更新(渲染)
*/
}, { timeout: 500 }))//500毫秒依然存活且不备执行,则放入js同步任务队列
break;
case 'list':
requestIdleCallback(() => {//空闲帧执行
/**
* 执行体 低频高性能消耗渲染列表更新(渲染)
*/
})
break;
default:
break;
}
}
下次批量初始化时,清空上次未执行完的任务
思路:在下次批量初始化时,清空上次未执行完的任务,减少因为执行上次的任务而阻塞当前任务的情况
能不能直接清空任务队列呢? 就不知道了
概念引入
requestAnimationFrame(fn)简单理解就是一个渲染帧完成自己任务后,如果还有时间剩余,则执行这个方法fn,后面就叫他差帧了
//主要代码
requestAnimationFrame(() => {//使用差帧清空真实队列
callBackArr.forEach((row: any) => {//清空真实队列
cancelIdleCallback(row)
})
callBackArr = []//清空虚拟队列
})
let callBackArr: Array<any> = []//空闲帧ID队列
//来自Worker的消息处理
const onmessageFn = (e: any) => {
const res = e.data
const { code, data } = res
switch (code) {
case 'data':
callBackArr.push(requestIdleCallback(() => {//空闲帧执行
/**
* 执行体 高频率单条数据更新(渲染)
*/
}, { timeout: 500 }))//500毫秒依然存活且不备执行,则放入js同步任务队列
break;
case 'list':
requestAnimationFrame(() => {//使用差帧清空真实队列-------------------------这里
callBackArr.forEach((row: any) => {
cancelIdleCallback(row)
})
callBackArr = []//清空虚拟队列
})
requestIdleCallback(() => {//空闲帧执行
/**
* 执行体 低频高性能消耗渲染列表更新(渲染)
*/
})
break;
default:
break;
}
}
问题产生:引入虚拟队列后,只加不减存在无效ID,如何剔除无效的空闲帧ID(已经执行的空闲帧方法已无效)
清空虚拟队列无效ID
-
空虚拟队列无效ID解决上一个问题
-
思路:当有下一个空闲帧时,说明,已经没有阻塞,虚拟队列内对应的空闲帧渲染已全部执行,所以直接清空
/**
* 清空阻塞关键
* 空闲帧清空空闲帧ID数组(虚拟队列)
* 如果有空闲帧,说明无阻塞,这时空闲帧队列已经全部执行,空闲帧ID数组内部对应任务是无效任务
*/
let callBackArrNullFn = () => {
requestIdleCallback(() => {
callBackArr = []//清空虚拟队列
callBackArrNullFn()//递归自身---期待下一个空闲帧
})
}
callBackArrNullFn()//执行---一个空闲帧才继续的计时器
设置阻塞上限-达到阻塞上限控制
问题:一旦遇到数据量大的整体,浏览器渲染压力增大,如果不及时处理,阻塞的内容只会越来越多,(虽然不会使页面卡顿,但不实时)
解决思路:监听虚拟队列,一旦虚拟队列长度达到阈值,说明阻塞在不断攀升,直接去除虚拟队列和真实队列,然后开始渲染最新的数据,(数据量大,达到阈值只是几百毫秒的事情,阈值按需求跳转)
/**
* 清空阻塞关键
* 差帧监听空闲帧渲染数组数据(虚拟队列)
* 如果数据量持续攀升,说明阻塞已经无法控制,这时立即清空空闲帧队列(真实队列),同时清空空闲帧渲染数组(虚拟队列)
* size 清空阈值 虽然是到达阈值就清空队列,但是可能因为无差帧导致实际超出100
*/
let size = 100
const watchArrRequestAnimationFrame = () => {//为什么用差帧?为了节省性能
requestAnimationFrame(() => {
if (callBackArr.length > size) {
callBackArr.forEach((row: any) => {//清空真实队列
cancelIdleCallback(row)//清空真实队列
})
callBackArr = []//清空虚拟队列
}
watchArrRequestAnimationFrame()//递归自身
})
}
watchArrRequestAnimationFrame()//差帧实现监听效果
整体代码
/**
* api提示:
* requestAnimationFrame()//差帧渲染
* requestIdleCallback()//空闲帧渲染
* cancelIdleCallback()//清空空闲帧渲染执行体(未执行的)
*/
let callBackArr: Array<any> = []//空闲帧ID队列
/**
* 清空阻塞关键
* 空闲帧清空空闲帧ID数组(虚拟队列)
* 如果有空闲帧,说明无阻塞,这时空闲帧队列已经全部执行,空闲帧ID数组内部对应任务是无效任务
*/
let callBackArrNullFn = () => {
requestIdleCallback(() => {
callBackArr = []//清空虚拟队列
callBackArrNullFn()//递归自身
})
}
callBackArrNullFn()//执行---一个空闲帧才继续的计时器
/**
* 清空阻塞关键
* 差帧监听空闲帧渲染数组数据(虚拟队列)
* 如果数据量持续攀升,说明阻塞已经无法控制,这时立即清空空闲帧队列(真实队列),同时清空空闲帧渲染数组(虚拟队列)
* size 清空阈值 虽然是到达阈值就清空队列,但是可能因为无差帧导致实际超出100
*/
let size = 100
const watchArrRequestAnimationFrame = () => {
requestAnimationFrame(() => {
if (callBackArr.length > size) {
callBackArr.forEach((row: any) => {//清空真实队列
cancelIdleCallback(row)//清空真实队列
})
callBackArr = []//清空虚拟队列
}
watchArrRequestAnimationFrame()//递归自身
})
}
watchArrRequestAnimationFrame()//差帧实现监听效果
//来自Worker的消息处理
const onmessageFn = (e: any) => {
const res = e.data
const { code, data } = res
switch (code) {
case 'data':
callBackArr.push(requestIdleCallback(() => {//空闲帧执行
/**
* 执行体 高频率单条数据更新(渲染)
*/
}, { timeout: 500 }))//500毫秒依然存活且不备执行,则放入js同步任务队列
break;
case 'list':
requestAnimationFrame(() => {//使用差帧清空真实队列
callBackArr.forEach((row: any) => {
cancelIdleCallback(row)
})
callBackArr = []//清空虚拟队列
})
requestIdleCallback(() => {//空闲帧执行
/**
* 执行体 低频高性能消耗渲染列表更新(渲染)
*/
})
break;
default:
break;
}
}
渲染优化
就是大概按着减少DOM数量,减少重排的方向去就ok了
我自己实现的是:可视区域渲染,即只渲染看得到的部分,这个很容易搜到,就不多说了.
关于帧优化,react有相关的api,建议使用react