今天从多个方面来记录一下自己项目中考虑过的优化方案
一、代码层面
首先从基本的代码层面来看,举个例子,比如我拿到了一个数组,需要对这个数组的内容做一些处理(比如需要对数组的内容遍历然后做一些处理等等),但是在当数组是空的时候就没有必要走下面的逻辑了。所以可以加一个判断语句,如果数组为空就不再进行后续的逻辑,这也可以减少很多代码的执行,从而提高性能。类似的还有很多,应该说是代码细节层面的一些优化了。
二、网络层面的优化
1、重复请求的取消:第一次请求还没回来之前就进行第二次同样的请求,这时候就应该把第二次请求给取消掉,防止重复请求 这里我们对axios请求进行了封装,采用了拦截器中进行了相应的处理。 在请求拦截器和响应拦截器之前,先实现了三个方法。 因为当请求方式,请求 URL和请求携带参数都一样时,就可以认为是相同的请求。因此在每次发起请求时,可以根据当前请求的请求方式、请求 URL地址和请求携带参数来生成一个唯一的 key;同时为每个请求创建一个专属的 CancelToken,然后将 key 和 Cancel 函数以键值对的形式保存到 Map对象中,使用 Map对象的好处是可以快速判断是否有重复的请求
generateReqKey(config){
const {method,url,params,data} = config;
return [method,url,qs.stringify(params),qs.stringify(data)].join('&')
}
PendingRequest = new Map();
addPendingRequest(config){
const requestKey = this.generateReqKey(config);
config.cancalToken = config.cancalToken || new axios.CancelToken((cancel)=>{
if(!this.PendingRequest.has(requestKey)){
this.PendingRequest.set(requestKey,cancel)
}
})
}
removeRequest(config){
const requestKey = this.generateReqKey(config);
if(this.PendingRequest.has(requestKey)){
const CancelToken = this.PendingRequest.get(requestKey);
CancelToken(requestKey);
this.PendingRequest.delete(requestKey);
}
}
然后是配置拦截器 因为需要对所有的请求都进行处理,因此考虑使用 axios的拦截器机制来实现取消重复请求的功能。 axios的制作者为开发人员提供了请求拦截器和响应拦截器,各自的作用如下: (1)请求拦截器:该类拦截器的作用是在请求发送前统一执行某些操作,比如在请求头中添加 token字段; (2)响应拦截器:该类拦截器的作用是在接收到服务器响应后统一执行某些操作,比如根据不同的状态码来执行相对应的操作。
interceptors(install) {
install.interceptors.request.use(config => {
this.removeRequest(config);
this.addPendingRequest(config);
const token = localStorage.getItem('token');
if (token) {
config.headers.authorization = `token ${token}`;
}
return config
}, err => {
return Promise.reject(err)
});
install.interceptors.response.use(res => {
this.removeRequest(res.config);
const { data } = res;
return data
}, err => {
this.removeRequest(err.config)
return Promise.reject(err)
});
}
完整的网络请求封装代码如下:
import axios from 'axios';
import qs from "qs"
class HttpRequest {
constructor(options) {
this.defaults = {
baseUrl: ''
}
this.defaults = { ...this.defaults, ...options }
}
generateReqKey(config){
const {method,url,params,data} = config;
return [method,url,qs.stringify(params),qs.stringify(data)].join('&')
}
PendingRequest = new Map();
addPendingRequest(config){
const requestKey = this.generateReqKey(config);
config.cancalToken = config.cancalToken || new axios.CancelToken((cancel)=>{
if(!this.PendingRequest.has(requestKey)){
this.PendingRequest.set(requestKey,cancel)
}
})
}
removeRequest(config){
const requestKey = this.generateReqKey(config);
if(this.PendingRequest.has(requestKey)){
const CancelToken = this.PendingRequest.get(requestKey);
CancelToken(requestKey);
this.PendingRequest.delete(requestKey);
}
}
interceptors(install) {
install.interceptors.request.use(config => {
this.removeRequest(config);
this.addPendingRequest(config);
const token = localStorage.getItem('token');
if (token) {
config.headers.authorization = `token ${token}`;
}
return config
}, err => {
return Promise.reject(err)
});
install.interceptors.response.use(res => {
this.removeRequest(res.config);
const { data } = res;
return data
}, err => {
this.removeRequest(err.config)
return Promise.reject(err)
});
}
request(options) {
options = { ...this.defaults, ...options }
const instance = axios.create(options);
this.interceptors(instance);
return instance
}
}
2、限制失败请求重试的次数,避免无效请求过多,导致大量的资源消耗 **3、善用缓存,减小网络请求的次数。**比如在我们的项目中,个人空间展示页,如果判断是自己访问自己的空间,那么就不用再去请求数据了,直接从vuex中或者localstorage中去取就可以了。
三、业务层面的优化
1、防抖节流 防抖节流是优化高频率执行代码的一种手段,比如浏览器的resize、scroll、keypress、mousemove等事件在触发的时候,会不断的调用绑定的回调函数,极大的浪费资源,甚至降低前端的性能。 为了优化体验,我们可以采取防抖和节流的手段来减少调用的频率。 节流:n秒内只运行一次,若n秒内重复触发,则只有一次生效。 防抖:n秒后执行该事件,若在n秒内重复触发,则重新计时。
代码实现如下: 节流函数
function throttled(fn,delay) {
let timer = null;
let startTime = Date.now();
return function(){
let curTime = Date.now();
let remaining = delay-(curTime-startTime);
let context = this;
let args = arguments;
clearTimeout(timer);
if(remaining<=0){
fn.apply(context,args);
startTime = Date.now();
}else{
timer = setTimeout(fn,remaining);
}
}
}
防抖函数
function debounce(fn,wait,immediate){
let timeout;
return function(){
let context = this;
let args = arguments;
if(timeout) clearTimeout(timeout);
if(immediate){
let callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
},wait)
if(callNow){
fn.apply(context,args)
}
}else{
timeout = setTimeout(function(){
fn.apply(context,args);
},wait);
}
}
}
二者的应用场景举例: 防抖是连续的事件,只需要触发一次回调: (1)搜索框搜索输入,只需要用户最后一次输入完再发送请求 (2)手机号、邮箱验证输入检测 (3)窗口大小resize,只需要窗口调整完成后,计算窗口大小,防止重复渲染 节流是每间隔一段时间执行一次回调: (1)滚动加载,加载更多或者滚动到底部监听 (2)搜索框,搜索联想功能
2.事件解绑 组件销毁之前解绑事件,避免内存占用过多
3.长列表优化 我们的项目中采用了分页的思想来分批请求数据。其余的还可以采用懒加载、虚拟滚动等。
4.首屏时间优化 可以采用预渲染技术,比如ssr等。 可以用lighthouse去测检测一下性能
5.路由懒加载
import Welcome from '@/pages/Welcome'
import Home from '@/pages/Home';
const Search = ()=> import('@/pages/Search');
const Community = ()=> import('@/pages/Community');
const MyRoom = ()=> import('@/pages/MyRoom');
** 6.组件懒加载** 用到了再加载,缩短加载时间
四、打包方面
(1)减少打包的体积:压缩代码、资源的体积 (2)缩短打包时长:cache-loader、thread-loader cache-loader: 允许缓存以下 loaders 到(默认)磁盘或数据库。在一些性能开销较大的 loader 之前添加 cache-loader,以便将结果缓存到磁盘里。保存和读取这些缓存文件会有一些时间开销,所以请只对性能开销较大的 loader 使用此 loader。
thread-loader: 使用时,需将此 loader 放置在其他 loader 之前。放置在此 loader 之后的 loader 会在一个独立的 worker 池中运行。 在 worker 池中运行的 loader 是受到限制的。例如: 这些 loader 不能生成新的文件。 这些 loader 不能使用自定义的 loader API(也就是说,不能通过插件来自定义)。 这些 loader 无法获取 webpack 的配置。 每个 worker 都是一个独立的 node.js 进程,其开销大约为 600ms 左右。同时会限制跨进程的数据交换。 请仅在耗时的操作中使用此 loader! (3)使用CDN:请求资源更快
|