datamember 无法创建子列表_面试官问: 渲染十万条数据的虚拟列表请你实现一下...

技术不局限于框架,相同的原理只是实现方式略有不同。

d6c11e53dad32b121e37c4013658ecca.gif

前置

1. 什么是虚拟列表?

首先本文实现的是一个无限长表,不是严格上的虚拟列表,虚拟列表只是一个概念,本人对虚拟列表这个表述不置可否。

虚拟列表是对于列表形态数据展示的一种按需渲染,是对长列表渲染的一种优化。

虚拟列表不会一次性完整地渲染长列表,而是按需显示的一种方案,以提高无限滚动的性能。

2. 虚拟列表的实现原理?

根据容器元素的高度 clientHeight 以及列表项元素的高度 offsetHeight 来显示长列表数据中的某一个部分,而不是去完整地渲染长列表。

f19ed72251e4db79c08f3033452a3803.png

实现一个虚拟列表需要:

  • 得知容器元素的高度 clientHeight
  • 得知列表项元素的高度 offsetHeight
  • 计算可视区域应该渲染的列表项的个数 count = clientHeight / offsetHeight
  • 计算可视区域数据渲染的起始位置 start
  • 计算可视区域数据渲染的结束位置 end
  • 对完整长列表数据进行截断 sliceList = dataList.slice(start, end)
  • 渲染截断后的列表数据,进而实现无限加载

3. 虚拟列表与懒加载有何不同?

懒加载与虚拟列表其实都是延时加载的一种实现,原理相同但场景略有不同。

  • 懒加载的应用场景偏向于网络资源请求,解决网络资源请求过多时,造成的网站响应时间过长的问题。
  • 虚拟列表是对长列表渲染的一种优化,解决大量数据渲染时,造成的渲染性能瓶颈的问题。

4. IntersectionObserver 介绍

IntersectionObserver 提供了一种异步观察目标元素与视口的交叉状态,简单地说就是能监听到某个元素是否会被我们看到,当我们看到这个元素时,可以执行一些回调函数来处理某些事务。

let io = new IntersectionObserver(callback, option);

callback 会触发两次。一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见)。

更多介绍Intersection Observer

实现

1. 生成十万条数据:

function getDataList() {
  let data = []
  for(let i = 0; i 100000; i++) {
    data.push({id: "item" + i, value: Math.random() * i})
  }
  return data;
}

2. Dom 创建及列表渲染:

不依赖框架的情况下,需要命令性的去创建 DOM 以及操作 DOM

<ul class="container">
  <span class="sentinels">....span>
ul>
function $(selector) {
  return document.querySelector(selector)
}

function loadData(start, end) {
  // 截取数据
  let sliceData = getDataList().slice(start, end)
  // 现代浏览器下,createDocumentFragment 和 createElement 的区别其实没有那么大
  let fragment = document.createDocumentFragment(); 
  for(let i = 0; i     let li = document.createElement('li');
    li.innerText = JSON.stringify(sliceData[i])
    fragment.appendChild(li);
  }
  $('.container').insertBefore(fragment, $('.sentinels'));
}

如果是基于框架,直接操作数据即可(Vue 伪代码):

// 父组件
"listData">virtual-list>// 子组件<ul class='container'><liv-for="item in sliceData" :key="item.id"
  >{{ item }}li>ul>
...// jsthis.sliceData = this.data.slice(start, index)

3. 使用 intersectionObserver API 创建监听器:

let count = Math.ceil(document.body.clientHeight / 120);
let startIndex = 0;
let endIndex = 0;
...
let io = new IntersectionObserver(function(entries) {
    loadData(startIndex, count)
    // 标志位元素进入视口
    if(entries[0].isIntersecting) {
      // 更新列表数据起始和结束位置
      startIndex = startIndex += count;
      endIndex = startIndex + count;
      if(endIndex >= getDataList().length) {
        // 数据加载完取消观察
        io.unobserve(entries[0].target)
      }
      // requestAnimationFrame 由系统决定回调函数的执行时机
      requestAnimationFrame(() => {
        loadData(startIndex, endIndex)
        let num = Number(getDataList().length - startIndex)
        let info = ['还有', num , '条数据']
        $('.top').innerText = info.join(' ')
        if(num - count <= 0) {
           $('.top').classList.add('out')
         }
      })
    }
  });
  // 开始观察“标志位”元素
  io.observe($('.sentinels'));
})

由于 IntersectionObserver 无法监听动态创建的 dom,所以我们设置一个「标志位」元素 span.sentinels 作为监听的目标对象。

<ul class="container">
  <span class="sentinels">....span>
ul>

如果目标元素正处于交叉状态 entries[0].isIntersecting == true,则代表 .sentinels 进入了可视区域,从而加载新的列表数据。

if(entries[0].isIntersecting) {
  ...
  requestAnimationFrame(() => {
    loadData(startIndex, endIndex)
  })
  ...
}

最后将新的列表 insertBefore 到其前面,进而实现无限加载。

$('.container').insertBefore(fragment, $('.sentinels'));


❤️ 爱心三连击

1.如果觉得这篇文章还不错,来个分享、点赞、在看三连吧,让更多的人也看到~

2.关注公众号超级全栈架构师,领取5000G全栈学习资料,定期为你推送新鲜干货好文,每周送书福利不要错过哟~

3.扫描下方添加微信,拉你进技术交流群和各大厂的同学一起学习交流成长 ~

8888f17132adaeff3a811d7faabfb538.png

05138d3603023e05268cc3c9da8a54a0.png你点的每个赞,我都认真当成了喜欢
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值