分时函数/requestAnimationFrame优化页面数据渲染

1,448 阅读3分钟

前言

本章探讨页面加载海量数据时,如何保证页面渲染流畅,不会出现因大量添加dom节点,导致浏览器卡死的情况。

实例

举例创建web好友列表,可能会有成千上百个好友,若一个好友由一个节点表示,当我们渲染这个列表时,可能需要一次性的创建n多节点。

<body>
  <ul id="list-with-big-data">1000个好友</ul>
</body>

分析

思路无非动态生成li插入ul中,并设定li的文本内容。

// 全局变量,先扔这儿
let [ary, ul] = [new Array(), document.querySelector('#list-with-big-data')];
// 生成好友数据,暂拟1000个
for(i = 1; i < 1000; i++ ){
  ary.push(i)
};

step1 基础做法

// 基础做法,无fuck说
function renderList() {
  for(let i = 0;i<ary.length;i++) {
    let li = document.createElement('li');
    li.innerHTML = ary[i];
    ul.appendChild(li)
  }
};
renderList();

基础做法虽能满足需求,然则实践中我们发现由于数据过多,页面渲染缓慢且造成卡顿。是故,就有了step2。

step2 基于js高阶函数之中的分时函数。 我们用renderData方法实现创创建节点的工作分批进行,将一次性创建1000节点,改为10毫秒创建8个节点。

step2 分时函数

function renderData(fn, count) {
  // renderData方法接受两个参数fn和count,fn为创建节点逻辑方法,count参数表示每批创建节点数量。
  // 每次遍历count个数据,传入节点创建函数,同时删除此数据。 
  let dataChunk = () => {
    for (i = 0; i < (Math.min(ary.length, count || 0)); i++) {
      let item = ary.shift();
      // 将每条数据作为参数传入节点创建函数中。
      fn(item)
    }
  };
  // 此处计时器设为10毫秒,当然也改为参数传入。 
  return () => {
    let timeOut = setInterval(function (params) {
    // dataChunk方法遍历数据时,逐一将数据删除,当数据为空时,结束计时器。
      if (ary.length === 0) {
        return clearInterval(timeOut)
      }
      dataChunk()
    }, 10)
  }
}

let renderList = renderData ((n) => {
  // 再简单不过的创建节点并插入的过程。 默认第二个参数即count为8,表一次渲染8条数据。
  let li = document.createElement('li')
  li.innerHTML = n
  ul.appendChild(li)
}, 8)
renderList();

至此便完成了数据批量渲染~,我们还可以做一些优化,比如requestAnimationFrame + fragment

step3 DocumentFragment + requestAnimationFrame

// step 3
function renderList(count = 8) {
  let dataChunk = function () {
    // 创建fragment,fragment详细可移步度娘。 vue双向绑定的实现大多也有用到。
    let fragment = document.createDocumentFragment();
    for (i = 0; i < (Math.min(ary.length, count || 0)); i++) {
      // 与step2大同小异,将节点创建移步此处,并逐条删除数据。
      let li = document.createElement('li');
      li.innerHTML = ary[0];
      // 私以为就是将动态生成的list节点挂载到fragment生成的虚拟dom上,之后再一并插入到ul底下。
      fragment.appendChild(li);
      ary.shift();
     };
     ul.appendChild(fragment);
     renderData()
  };
  var renderData = () => {
    if (ary.length > 0) {
      // 此处调用requestAnimationFrame方法,将dataChunk作为参数传入,实现递归,反复的一进一出渲染数据。
      window.requestAnimationFrame(dataChunk)
     }
  }
  renderData();
}
renderList();

step3 的实现基于DocumentFragment,我们知道可以通过 DocumentFragment 的使用,减少 DOM 操作次数,降低回流对性能的影响。并且可以通过 requestAniminationFrame 保证插入新节点操作在页面重绘前执行,二者结合可以实现数据渲染优化。

关于requestAnimationFrame

【1】requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率。
【2】在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量。
【3】requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销