什么是性能优化:从输入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过多会导致,页面闪退。(尤其是移动端)
文件上传的性能优化
-
- 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>
);
}
}