前言

最近做项目时遇到一个渲染问题,经过查找多处资料后看到一个新名词分时函数 ,接下来就总结一篇我看到的、学到的东西。

开始

什么是分时函数呢?我觉得大家在项目中也会遇到这种情况:就是有时候是我们主动需要大量的频繁调用某个函数,比如你需要向页面插入1000个元素乃至更多元素,在短时间内往页面中大量添加 DOM节点显然会让浏览器吃不消,我们看到的结果往往就是浏览器的卡顿甚至假死。

但是如果把这个1000个元素分批绘制, 那这个时候浏览器的压力就小了。 这个就是分时,那分时函数当然就是咱们把逻辑封装成函数了。

代码示例

我们来举一个简单的例子:

const btn = document.querySelector('.btn');
const datas = new Array(1000000).fill(0).map((_, i) => i); // 生成1000000个数组元素

// 点击代码后循环数组,向页面插入div元素
btn.onclick = () => {
	for(const i of datas){
		const el = document.createElement('div');
		el.innerText = i;
		document.body.appendChild(el);
	}
}

页面中有个按钮,点击后向页面中插入1000000个div元素,代码很简单。你会发现点击按钮后页面卡住了,等了很长时间页面才有反应。

这里我们简单说一下造成卡顿的原因:

1A998612-A4B6-4504-BF53-0E266BC75D24.png

如上图,我们知道浏览器是每16.6ms渲染一次,但是现在呢,有一个任务需要的时间比较长,没有给浏览器留下任何的空间进行渲染,所以就导致页面的卡顿。

所以我们最好把这个任务进行分片,不要一次性去执行,等一下浏览器渲染,执行完一片后再去执行下一分片,这样就不会造成卡顿了。

代码封装

const btn = document.querySelector('.btn');
const datas = new Array(1000000).fill(0).map((_, i) => i); // 生成1000000个数组元素

// 点击代码后循环数组,向页面插入div元素
btn.onclick = () => {
  const taskHandler = (i) => {
    const el = document.createElement('div');
    el.innerText = i;
    document.body.appendChild(el);
  }
  browserScheduler(datas, taskHandler);
}

// 分片执行
function performChunk(data, taskHandler, scheduler) {
  // 允许直接传执行次数
  if (typeof data === 'number') {
    data = {
      length: data
    }
  }
  if (data.length === 0) {
    return;
  }
  let i = 0;

  // 开启下一分片的执行
  function _run() {
    if (i >= data.length) {
      return;
    }
    scheduler((signal) => {
      while (signal() && i < data.length) {
        taskHandler(data[i], i);
        i++;
      }
    });
    // 一个渲染帧中,空闲时分片执行
    requestIdleCallback((idle) => {
      while (idle.timeRemaining() > 0 && i < data.length) {
        taskHandler(data[i], i);
        i++;
      }
      // 此次分片执行完成,执行下一分片
      _run();
    })
  }

  _run();
}

// 浏览器环境,使用requestIdleCallback
function browserScheduler(data, taskHandler) {
  const scheduler = (task) => {
    return requestIdleCallback((idle) => {
      task(() => idle.timeRemaining() > 0);
    });
  }
  performChunk(data, taskHandler, scheduler)
}

// node环境,使用setImmediate
function nodeScheduler(data, taskHandler) {
  const scheduler = (task) => {
    return setImmediate(() => {
      task(() => true);
    });
  }
  performChunk(data, taskHandler, scheduler)
}

// 其他环境 ...

这里列举了 requestIdleCallback setImmediate ,也可以其他方式如 setTimeout 等方式,根据需求修改 scheduler。

结语

分时函数作为一种有效的性能优化手段,其核心在于通过控制函数执行的节奏,减轻瞬时压力,确保用户界面的流畅性与系统的响应速度。

掌握并合理运用分时函数策略,对于提升Web应用的用户体验至关重要。在未来面对高性能需求及优化挑战时,希望本文介绍的方法能成为你开发工具箱中的又一利器。