分时函数与节流函数一样,是用来解决函数频繁调用带来的性能问题。
节流函数就像是英雄联盟中的英雄技能每用一次都会进入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()
}
那么到此咱们的分时函数就封装完成了,要是小伙伴们感兴趣的话可以继续在封装。如有错误也请大佬们指出。