🎇高频-🧨大量数据-🎑渲染-✨优化记录🎉

672 阅读7分钟

数据环境

  • 高频率: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