3

问题

以前在面试中遇到过一个这样的问题:如果给你数据量很大比如十万条,后台又没有分页的功能,那么你怎么操作能减轻浏览器的压力或者说怎样能更流畅的渲染出来。

很明显这个问题属于那种没事找事型的,那么既然问出来了,我们还是得探一探究竟。

1.分片渲染

分片渲染顾名思义就是数据分开渲染,这样解决了一次性渲染所带来的卡顿。

实现原理

根据EventLoop原理实现的,代码执行放入执行栈中,同步代码先执行,遇到宏任务放入宏任务队列,遇到微任务放到微任务队列,等同步代码执行完毕之后清空微任务队列,然后再将宏任务队列的第一项放入执行栈中,再进行以上的执行过程,这就形成了事件环机制(每循环一次会执行一个宏任务,并清空对应的微任务队列)

代码实现
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>分片渲染</title>
  </head>
  <style>
    #app {
      width: 100px;
      height: 300px;
      overflow: auto;
    }
  </style>
  <body>
    <div id="app"></div>
  </body>
</html>
<script>
  let index = 0;
  let total = 100;
  let id = 0;
  function load() {
    index += 10;
    if (index <= total) {
    //定时器属于宏任务,它的执行顺序是等上一个宏任务也就是上一个定时器执行完成之后才能执行
      setTimeout(() => {
        for (let i = 0; i < 10; i++) {
          let item = document.createElement("div");
          item.innerHTML = id++;
          app.appendChild(item);
        }
    //使用递归的方式
        load();
      }, 1000);
    }
  }
  load();
</script>

上面也说到了分片渲染比一次性渲染的性能更好,但是dom元素还是那么多,实质上并不是最优的解决办法,那么我们来看一看虚拟列表。

2.vue虚拟列表

image.png
image.png
由上面两张图片可以看出虚拟列表就是只显示滚动条对应视口的相应数据。

实现虚拟列表的思路
1.由于虚拟列表只显示视口的数据,所以不会显示出滚动条,那么我们需要造一个滚动条。
2.造出滚动条之后元素就会被挤到下面 我们需要设置一个相对于父元素的定位。
3.由于设置了定位,当父元素滚动的时候,设置了定位的子元素会相对应的移动,那么我们需要在滚动的时候给子元素动态的设置top的值
4.获取到数据之后,需要根据视口来筛选要显示的数据
代码实现

这个demo是使用vue-cli生成的脚手架。
home.vue

// size:每一个item的高度   remain:要显示几个  items:获取到的数组
<template>
  <div id="home">
    <virtual-list :size='40' :remain='8' :items="items"></virtual-list>
  </div>
</template>
<script>
import VirtualList from "../components/VirtualList.vue";
export default {
  components: {
    "virtual-list": VirtualList,
  },
  data() {
    return {
      items: [],
    };
  },
  created() {
    for (let i = 0; i < 10000; i++) {
      this.items.push({ name: "我是第" + i + "个", id: i });
    }
  },
};
</script>

virtualList.vue

<template>
  <div id="VirtualList" ref="VirtualList" @scroll="handleScroll">
    //滚动条显示
    <div :style="{ height: items.length * size + 'px' }"></div>
    <div id="container" ref="container" :style="{ top: offsetTop }">
      <div
        v-for="(v, i) in itemList"
        :key="i"
        class="item"
        :style="{ height: size + 'px' }"
      >
        {{ v.name }}
      </div>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    size: { type: Number },
    remain: { type: Number },
    items: { type: Array },
  },
  computed: {
    itemList: function () {
      return this.items.slice(this.start, this.end);
    },
  },
  data() {
    return {
      start: 0,
      end: this.remain,
      offsetTop: 0,
    };
  },
  methods: {
    handleScroll() {
        //从第几个item开始显示
      this.start = this.$refs.VirtualList.scrollTop / this.size;
        //开始位置加上要显示几个 = end
      this.end = this.start + this.remain;
        //动态更改定位的top值,保证不会随父元素一起滚动
      this.offsetTop = this.$refs.VirtualList.scrollTop + "px";
    },
  },
  mounted() {
    this.$refs.VirtualList.style.height = this.size * this.remain + "px";
  }
};
</script>
<style>
#VirtualList {
  overflow: auto;
  width: 200px;
  position: relative;
}
#container {
  width: 100%;
  position: absolute;
  left: 0;
  top: 0;
}
.item {
  line-height: 40px;
  box-sizing: border-box;
  border-bottom: 1px solid #ccc;
}
</style>

mengyuhang4879
13 声望7 粉丝