# 优化背景

如果应用存在非常长的或者无限滚动的列表,那么需要采用窗口化的技术来优化性能,只需要渲染少部分的内容(可视区域),减少重新渲染组件和创建dom节点的时间

# 使用自定义指令,来实现无线滚动

Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。 自定义指令 (opens new window)

<!-- 底部bottom可见时,执行fetchNext 加载下一页数据 -->
<div class="bottom" v-intersect="{ handler: fetchNext }"></div>
1
2
// 自定义指令 src\directive\intersect
// 观察元素不可见的时候不调用获取列表的函数
// 在inserted钩子函数中观察元素,并且要注意给el一个属性,使其可以在unbind中也获取到observer,方便unbind钩子函数中停止观察
const intersect = {
  inserted(el, binding) {
    const value = binding.value;
    const { handler, options = {} } = value;

    const observer = new IntersectionObserver((entries = [], observer) => {
      if (!el._observe) return;

      if (handler && el._observe.init) {
        const isIntersecting = Boolean(
          entries.find((entry) => entry.isIntersecting)
        );

        if (isIntersecting) {
          handler(entries, observer, isIntersecting);
        }
      }

      el._observe.init = true;
    }, options);

    el._observe = { init: false, observer };

    observer.observe(el);
  },
  unbind(el) {
    if (!el._observe) return;

    el._observe.observer.unobserve(el);
    delete el._observe;
  },
};

export default intersect;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

IntersectionObserver API (opens new window)

# 长列表优化

但是这个时候会发现一个问题,就是随着列表的长度的增加,DOM节点也会增加,当DOM节点到达一定数量的时候,会发生卡顿,所以接下来就要对它进行优化了

<template>
  <div class="x-infinite" ref="container" :style="{padding: padding}">
    <slot :sliceItems="sliceItems"></slot>
  </div>
</template>

<script>
import { throttle } from "../util/throttle";

function getScrollTop() {
  return document.documentElement.scrollTop || document.body.scrollTop;
}

export default {
  props: {
    items: {
      required: true
    },
    itemHeight: {
      required: true,
      type: Number
    }
  },
  data() {
    return {
      buffer: 5, //优化用户体验,一个过度的作用
      scrollTop: 0, //document.body.scrollTop 网页被卷去的高
      viewportHeight: 0 //window.innerHeight返回窗口的文档显示区的高度
    };
  },
  computed: {
    // 计算属性获得over(数组的开始索引) ,down(数组的结束索引),sliceItems(需要渲染的数组)padding(div样式用来减少div渲染列表的区域)这些数据
    over() {
      return Math.max(
        Math.floor(this.scrollTop / this.itemHeight) - this.buffer,
        0
      );
    },
    down() {
      return Math.min(
        Math.ceil(
          (this.scrollTop + this.viewportHeight) / this.itemHeight + this.buffer
        ),
        this.items.length
      );
    },
    sliceItems() {
      return this.items.slice(this.over, this.down);
    },
    padding() {
      return `${this.over * this.itemHeight}px 0 ${Math.max(
        (this.items.length - this.down) * this.itemHeight,
        0
      )}px 0`;
    }
  },
  created() {
    this.scrollTop = getScrollTop();
    this.viewportHeight = window.innerHeight;

    // 监听浏览器滚动
    document.addEventListener("scroll", this.onScroll, {
      passive: true
    });
  },
  destroyed() {
    document.removeEventListener("scroll", this.onScroll);
  },
  methods: {
    // 节流也可以减少对DOM的操作,对DOM进行一定的优化
    onScroll: throttle(function() {
      this.scrollTop = getScrollTop();
      this.viewportHeight = window.innerHeight;
    })
  }
};
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78