如何更优的渲染一万条数据,面试强心剂

882 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

大概是上个月刷到一道笔试题,让手写出,当渲染一万条数据时,前端应该怎么渲染到页面上,目前来看这道题的出现率还是很高的,主要是跟性能优化有关,这里具体讲一下

啊那现在我们先渲染一万条出来,分批处理,并利用setTimeout把逻辑推到宏任务队列中

<div>
    <ul class="list"></ul>
</div>
<script>
  const total = 10000,
  // 每次处理20条
  each = 20,
  // 需要处理次数
  needTimes = Math.ceil(total / each),
  content = document.querySelector('.list')
  // 当前处理次数
  let currentTime = 0
  function add() {
    setTimeout(() =>{
     // 分批处理
     for (let i = 0; i < each; i++) {
       const li = document.createElement('li')
       li.innerText = Math.floor(i+currentTime*each)
       content.appendChild(li)
      }
      currentTime++;
      // 循环处理
      if (currentTime < needTimes) add();
    },1000/60)
  }
  add();
</script>

这样只是能渲染出来,但因为每次循环都会操作一次dom,造成性能上会差点,测试页面Load为190ms

这里利用 createdocumentfragment() 修改一下
createdocumentfragment()方法创建了一虚拟的节点对象,节点对象包含所有属性和方法
通过循环把每次分批处理的节点添加到这个节点对象中,然后添加到dom中,这样每次进入方法,每次进入方法后重排的次数就从each次变为了1次
这里是为了减少操作dom的次数

<div>
    <ul class="list"></ul>
</div>
<script>
  const total = 10000,
  // 每次处理20条
  each = 20,
  // 需要处理次数
  needTimes = Math.ceil(total / each),
  content = document.querySelector('.list')
  // 当前处理次数
  let currentTime = 0
  function add() {
    setTimeout(() =>{
     // 创建一个虚拟的节点对象
     const fragment = document.createDocumentFragment()
     // 分批处理
     for (let i = 0; i < each; i++) {
       const li = document.createElement('li')
       li.innerText = Math.floor(i+currentTime*each)
       fragment.appendChild(li)
     }
     把虚拟的节点对象添加到主节点中
     content.appendChild(fragment)
     currentTime++;
     // 循环处理
     if (currentTime < needTimes) add();
    },1000/60)
  }
  add();
</script>

这里测试Load只有90ms

但这里还不是最优的的方案,setTimeout这里其实是属于写死了的,并且还会受任务队列的影响,这样不利于这样高频次的刷新,于是需要修改一下
更换成window.requestAnimationFrame() 会更优,为什么

  1. 因为回调函数执行次数通常与浏览器屏幕刷新次数相匹配,不会引起丢帧
  2. 窗口没激活时,动画将停止,省计算资源
  3. 作为浏览器专门为处理动画提供的api,经过浏览器优化,动画更流畅

注意:若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用window.requestAnimationFrame()
兼容:IE10以上

<div>
    <ul class="list"></ul>
</div>
<script>
  const total = 10000,
  // 每次处理20条
  each = 20,
  // 需要处理次数
  needTimes = Math.ceil(total / each),
  content = document.querySelector('.list')
  // 当前处理次数
  let currentTime = 0
  function add() {
     // 创建一个虚拟的节点对象
     const fragment = document.createDocumentFragment()
     // 分批处理
     for (let i = 0; i < each; i++) {
       const li = document.createElement('li')
       li.innerText = Math.floor(i+currentTime*each)
       fragment.appendChild(li)
     }
     把虚拟的节点对象添加到主节点中
     content.appendChild(fragment)
     currentTime++;
     // 循环处理
     if (currentTime < needTimes) window.requestAnimationFrame(add);
  }
  window.requestAnimationFrame(add)
</script>

测试Load降低到了最低30ms