分时函数(TimeChunk)

57 阅读3分钟

分时函数与节流函数一样,是用来解决函数频繁调用带来的性能问题。

节流函数就像是英雄联盟中的英雄技能每用一次都会进入cd状态,当cd转好以后才可以再次使用。而分时函数就是函数一段一段的执行,具体下一次分片什么时候开始和每次分片执行多少都是可控的。

例如在短时间内往页面中添加大量的DOM节点,往往浏览器就会卡顿,甚至假死。

//在页面中插入10万条元素,就会看到明显的卡顿
const btn = document.querySelector('.btn')

const datas = new Array(100000).fill(0).map((_,i)=>i)
btn.onclick=()=>{
  for(const i of datas) {
  const div = document.createElement('div')
  div.innerHTML = i
  document.body.appendChild(div)
  }
}

这个问题的解决方案之一就是使用分时函数,让创建节点的工作分批进行。

在浏览器当中,渲染一帧的时间是16.6ms,16.6ms够浏览器做许多的事情,甚至还有富裕,我们就可以利用这剩余的时间去分段执行我们要执行的代码。

我们如何应用浏览器每一帧的剩余时间呢?浏览器有一个方法叫requestIdleCallback,这个方法可以获取到浏览器的每帧剩余时间。

function performChunk(datas){
    //先进行边界判断
    if(datas.length===0){
        return;
    }
    let i = 0;
    //我们要执行的任务
    function _run(){
        if(i>=datas.length){
            return;
        }
        //一个渲染帧中,空闲时开启分片执行
        requestIdleCallback((idle)=>{
            // idle.timeRemaining()可以获取到一个渲染帧中的剩余时间
            while(idle.timeRemaining()>0&&i<datas.length){
                  const div = document.createElement('div')
                  div.innerHTML = i
                  document.body.appendChild(div)
                  i++
            }
            //此次分片完成
        })
    }
    _run()
}

到了这一步已经基本实现了分片功能,我们还可以完善一下,将其封装为一个更加通用的方法。

现在实现的是一个固定的功能,我们将要实现的功能抽离出来,以便实现不同任务的分时处理。给performChunk函数添加一个参数,taskhandler是要处理的任务。

function performChunk(datas,taskhandler){
    //先进行边界判断
    if(datas.length===0){
        return;
    }
    let i = 0;
    //我们要执行的任务
    function _run(){
        if(i>=datas.length){
            return;
        }
        //一个渲染帧中,空闲时开启分片执行
        requestIdleCallback((idle)=>{
            // idle.timeRemaining()可以获取到一个渲染帧中的剩余时间
            while(idle.timeRemaining()>0&&i<datas.length){
                taskhandler()
                i++
            }
            //此次分片完成
        })
    }
    _run()
}

上面我们说了,requestIdleCallback是浏览器方法。要是在node环境中呢?或者我要以我的方式来进行分时处理呢?

我们可以在传入一个调度器scheduler,来定义分时方式。

function performChunk(datas,taskhandler,scheduler){
    //先进行边界判断
    if(datas.length===0){
        return;
    }
    let i = 0;
    //我们要执行的任务
    function _run(){
        if(i>=datas.length){
            return;
        }
        //用scheduler来进行分时。
        scheduler((goOn)=>{
            // goOn函数用于判断是否还能往后执行。
            //每个分片执行多少也是依赖goOn
            while(goOn()>0&&i<datas.length){
                taskhandler()
                i++
            }
            //此次分片完成
        })
    }
    _run()
}

那么到此咱们的分时函数就封装完成了,要是小伙伴们感兴趣的话可以继续在封装。如有错误也请大佬们指出。