性能优化

277 阅读6分钟

什么是性能优化:从输入url到返回的页面 都可以作为性能优化的点
通用的性能优化的策略分为两点:

  • 文件加载的更快
    • 打包、压缩、缓存
  • 代码层面 (前端特有的性能优化层面)
    • SSR 同构
    • 虚拟列表 (virtual-list)
    • vdom(虚拟dom)

同构

我们的项目无论是vue、react大体分为(component、router、store(数据流))
同构的意思:意思就是 首屏SSR(服务端渲染),后面的交互CSR(客户端渲染) 同构的应用: 我们的项目====通过webpack(server.config/client.config)====》打包成一个sever.boudle一个client.boudle 后端是node
用户首次进入走的是sever.boudle 接下来的交互走的是client.boudle

同构的好处: SEO、首屏的加载速度都有很大的提升

缺点

  • 1本来我们node.js的服务可以不存在的(我们用ngix静态服务就可以)这样就凭空多了个node 这样我们的维护成本就会增加
  • 2.加大了服务端的压力
    • 之前我们项目解析html实在浏览器端进行的,我们把组件之类的都分摊在浏览器端去进行,这样我们加载的速度会慢一点
    • 如果我们做了SSR这样我们所有的模板解析,组件解析都在服务器端进行,这样服务器端的压力会很大(尤其是在我们做什么活动的时候)

SSR服务端压力大的问题怎么解决

服务端压力的问题怎么解决 我们的component、router、store渲染成html 在CSR是浏览器执行 在SSR就是服务器了就会让服务的计算量变多

  • 负载均衡/转发
    • 可以在多台机器上部署,一台机器上做负载/转发,把SSR部署在多台机器上
  • 服务降级
    • 如果流量很大的是偶,可以监控服务的参数(内存CPU内存吃紧的时候)降级为CSR(首屏也走CSR)
  • 利用缓存
    • 就是用户第一次进入页面之后把一些信息缓存起来 (拿空间换时间)
    • 那么如果是多个SSR 缓存 怎么命中
    • 利用哈希函数,每次用户访问的都是同一台机器
    • 那么如果 某一天机器挂掉了,总数就变了 怎么办?(缓存雪崩)
    • 一致性哈希
      • 数据 通过 哈希函数 映射成固定的字符串(可以理解为另类加密)
      • 最简单的哈希函数就是把每个字符的ASCK码拿出来 相加 求17的余数,这样我们可以稳定的得到一个0-17之间的正整数,这是一个单向的
      • 如果使用这样的函数,我们在分发机器的时候就可以得到稳定的去输出,用户来了算一次哈希,如果我们对20台机器,我们就对20去余,这样他一定会命中其中之一
      • 那么如果其中一台机器挂了,这样就变成了19台,那么索引的值就错了,产生缓存雪崩
    • 一致性哈希就是我们把 改成一个环形的,每人负责一小块,如果其中一个挂了,旁边的两个来接手,这样整个哈希环就不变的

长列表问题优化

如果像淘宝一样做无限的数据加载,有成千上万条数据,内存还可以接受,但是dom 任何的操作都是极其消耗性能的 ,因为它的属性太多了,对dom的任何操作都是性能上的黑洞。
如果像解决这样的问题,我们就应该尽量的少去渲染dom,怎么减少dom呢?就是一个视窗

什么是虚拟列表:即使根据可视区域去渲染列表的某一部分

虚拟列表的优点

  • 如果dom过多会导致,页面闪退。(尤其是移动端)

文件上传的性能优化

    1. axios、post再加一个uploadProgress的进度条。
  • 2.类型判断、大小判断、图片的尺寸判断。
    • 2.1 文件名.splice //这样不好因为如果文件改名了,你就不能得到真正的类型。
    • 2.2 文件头信息获取文件类型(二进制) 这个校验才是真正的方法。
import React, { Component } from "react";
/**
 *  实现步骤
 *  1. 定义一个子组件
 *      1.1 通过数组Array.fill 创建100个假数据
 *  2. 定一些些初始数据
 *      start end height scrollTop 滚动的距离 vData 可视区域数据 topOffset 上padding bottomOffset anchor
 *  3. 计算上下padding让滚动条看起来正常
 *      3.1 上padding  需要顶一个一个anchor 就是一个锚点 第一个元素的位置 top距离上面的距离 bottom下面的距离 index
 *      3.2 下padding  计算下padding 的时候 为什么要用 this.fakeData,length -this.end 是因为 end是动态变化的
 *  4. 滚动时改变 item里数据
 *      4.1 子元素向父元素传递 node和index 子元素通过 ref 获取到node 传递给父元素
 *      4.2 父元素接收到node后 获取到node的距离 getBoundingClientRect() 方法
 *      4.3 把所有元素的上下距离和index保存在cache中
 *      4.4 在滚动事件中 获取index下标 遍历cache 找出第一个node的bottom超过scrollTop元素的下标
 *      4.5 下标赋值给start end在通过start+count
 *      4.6 在滚动事件中在调用获取可视窗数据的函数
 *  5. 这样虽实现了但是上下会有一些留白,所以上下需要加一些buffer缓存的数据
 *      5.1如果每个高度不是固定的怎么办
 *      5.1如果高速时固定的,但是每个有图片,loadding后才加载出来
 *      5.3滚动动效
 *
 */

export default class VirtualList2 extends Component {
  constructor(props) {
    super(props);
    this.state = {
      vData: [],
      topOffset: 0, // 上padding
      bottomOffset: 0 // 下 padding
    };
    this.start = 0;
    this.end = 0;
    this.height = 60;
    this.fakeData = new Array(100).fill("skr");
    this.scrollTop = 0;
    // 锚点 也就是记录第一个元素
    this.anchor = {
      top: 0,
      bottom: 0,
      index: 0
    };
    // 缓存数据
    this.cache = [];
  }
  componentDidMount() {
    // 获取可视区域显示子组件的个数
    this.count = Math.ceil(window.innerHeight / this.height);
    this.end = this.start + this.count;
    // 获取可视区域的数据
    this.getVDataHandle();
  }
  getVDataHandle() {
    const vData = this.fakeData.slice(this.start, this.end);
    // 计算上下padding 让滚动条看起来正常 为什么要用 this.end 因为 end是会动态变得
    const bottomOffset = (this.fakeData.length - this.end) * this.height;
    this.setState({
      vData,
      bottomOffset,
      topOffset: this.anchor.top
    });
    // 定义一个滚动事件
    window.addEventListener("scroll", this.handleScroll);
  }
  handleScroll = () => {
    if (!this.doc) {
      this.doc = window.document.body;
    }
    const scrollTop = window.scrollY;
    this.scrollTop = scrollTop;
    this.getIndex(scrollTop);
    this.getVDataHandle();
  };
  getIndex(scrollTop) {
    console.log(scrollTop);
    console.log(this.cache);
    const anchor = this.cache.find(item => item.bottom >= scrollTop);
    if (!anchor) {
      return;
    }
    this.anchor = { ...anchor };
    this.start = this.anchor.index;
    this.end = this.start + this.count;
  }
  // 通过子元素传递过来的数据
  cacheOffset = (el, index) => {
    // 计算 el元素的距离
    const rect = el.getBoundingClientRect();
    const top = rect.top + window.pageYOffset;
    this.cache.push({
      top,
      bottom: top + this.height,
      index
    });
  };
  render() {
    const { vData, topOffset, bottomOffset } = this.state;
    return (
      <div
        style={{
          paddingTop: `${topOffset}px`,
          paddingBottom: `${bottomOffset}px`
        }}
      >
        {vData.map((item, index) => {
          return (
            <Item
              key={this.start + index}
              index={this.start + index}
              cacheOffset={this.cacheOffset}
            ></Item>
          );
        })}
        <Item></Item>
      </div>
    );
  }
}

class Item extends Component {
  componentDidMount() {
    // 这里希望 把自己的位置 传递个父元素
    // this.props.cacheOffset(this.node, this.index);
    this.props.cacheOffset &&
      this.props.cacheOffset(this.node, this.props.index);
  }
  render() {
    return (
      <div
        className="item"
        ref={node => {
          return (this.node = node);
        }}
      >
        <p>{this.props.index}测试数据</p>
        <p>skr</p>
      </div>
    );
  }
}