前言
本文整理了前端性能优化方面方法总结,如果对答案有不一样见解的同学欢迎评论区补充讨论,当然有问题,也欢迎在评论区指出。
一、什么是性能?
对于前端而言,从网站开始生成时,到代码运行中间,消耗浏览器以及服务器的所有资源
二、分析下前端加载速度慢原因
- 首先安装webpack的可视化资源分析工具
npm i webpack-bundle-analyzer -D
- 然后在webpack的dev开发模式配置中,引入插件
const BundleAnalyzerPlugin = require('webpack-bundle-plugin').BundleAnalyzerPlugin plugins: [ new BundleAnalyzerPlugin() ]
- 最后命令行执行
npm run build --report
, 浏览器会自动打开分析结果
其它性能监控方式
- (1)最简单的性能监控
<!DOCTYPE html>
<html>
<head>
<title></title>
<script type="text/javascript">
// 记录页面加载开始时间
var timerStart = Date.now();
</script>
<!-- 加载静态资源,如样式资源 -->
</head>
<body>
<!-- 加载静态JS资源 -->
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function() {
console.log("DOM 挂载时间: ", Date.now() - timerStart);
// 性能日志上报
});
window.addEventListener('load', function() {
console.log("所有资源加载完成时间: ", Date.now()-timerStart);
// 性能日志上报
});
</script>
</body>
</html>
- (2)浏览器的Performance
三、如何优化性能
1、缓存策略
2、精炼js代码
主要是用最优算法,来降低时间和空间复杂度
3、静态资源加载
(1)CDN
如:vue项目引入Element组件库
<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
(2)加载图片等资源
-
精灵图或雪碧图:将所有小图放置在一张大图上,使用时,直接引用大图定位即可
- 可以使用webpack插件:webpack-spritesmith
- 程序自动读取雪碧图目录下的文件,并将所有图片组合成一张大图
-
图片懒加载:按需加载
- 可以使用vue-lazy-load插件
//main.js import Vue from 'vue' import App from './App.vue' import VueLazyload from 'vue-lazyload' Vue.use(VueLazyload) // or with options Vue.use(VueLazyload, { preLoad: 1.3, error: 'dist/error.png', loading: 'dist/loading.gif', attempt: 1 }) new Vue({ el: 'body', components: { App } })
- vue文件中将需要懒加载的图片绑定 v-bind:src 修改为 v-lazy
<ul> <li v-for="img in list"> <img v-lazy="img.src" > </li> </ul>
4、vue框架代码优化
- 合理使用
v-if
和v-show
- 合理使用
computed
和watch
和filter
- 尽量减少使用watch监听相应的数据,监听数据量过多,会导致性能消耗,系统出现卡顿。采用事件中央总线或者vuex进行数据的变更操作。
v-for
遍历为item
添加key
,v-for
遍历避免同时使用v-if
- 细分组件:把所有的组件的布局写在一个组件中,当数据变更时,由于组件代码比较庞大,vuejs的数据驱动视图更新比较慢,造成渲染比较慢
- 定时器和监听器,在不使用时,需要在组件销毁阶段(beforeDestory)清除定时器和监听器removeEventListener或者clearInterval等
- keep-alive缓存组件,Props:
include
- 字符串或正则表达式。只有名称匹配的组件会被缓存。exclude
- 字符串或正则表达式。任何名称匹配的组件都不会被缓存。max
- 数字。最多可以缓存多少组件实例。
- 路由懒加载:三种方式
- SSR服务端渲染:局限性就是目前仅支持Koa、express等Nodejs的后台框架
- tree-shaking:移除无用代码
5、webpack层面优化
(1)开发环境性能优化
-
优化打包构建速度:
- HMR:hot module replacement 模块热替换,当一个模块变化,只会打包这一个模块
-
优化代码调试
- 优化代码调试source-map:能定位到代码错误的地方
(2)生产环境性能优化
- tree-shaking之webpack:去除无用代码
前提:1. 必须使用ES6模块化 2. 开启production环境
作用: 减少代码体积
在package.json中配置
"sideEffects": false 所有代码都没有副作用(都可以进行tree shaking)
问题:可能会把css / @babel/polyfill (副作用)文件干掉
"sideEffects": ["*.css", "*.less"] 这样就会保留css、less文件
-
开启gizp压缩 gizp压缩是一种http请求优化方式,通过减少文件体积来提高加载速度。html、js、css文件甚至json数据都可以用它压缩,可以减小60%以上的体积。前端配置gzip压缩,并且服务端使用nginx开启gzip,用来减小网络传输的流量大小。(compression-webpack-plugin)
-
压缩css文件
使用mini-xss-extract-plugin提取CSS 到单独的文件, 并使用optimize-css-assets-webpack-plugin来压缩CSS文件
- webpack的模块懒加载/预加载
console.log('index.js文件被加载了~');
// import { mul } from './test';
document.getElementById('btn').onclick = function() {
// 懒加载~:当文件需要使用时才加载~ ---- 但当使用某个模块,模块又非常大时,加载的就会慢
// 预加载 prefetch:会在使用之前,提前加载js文件,使用时读取缓存 ---- 兼容性较差
// 正常加载可以认为是并行加载(同一时间加载多个文件)
// 预加载 prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
console.log(mul(4, 5));
});
};
- PWA 渐进式网络开发应用程序(离线可访问)
- 在离线情况下,会从serviceWorker中和CacheStorage中获取资源
在真实项目中,当路由已经跳转,而上一页的请求还在pending状态,如果数据量小还好,数据量大时,跳到新页面,旧的请求依旧没有停止,这将会十分损耗性能,这时我们应该先取消掉之前还没有获得相应的请求,再跳转页面。这里axios给我们提供了一个方法:
cancelToken
官网方法一:
var CancelToken = axios.CancelToken;
var source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).catch(function(thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// 处理错误
}
});
// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');
如果我要跳转页面的话,我调用source.cance()方法就可以干掉之前这个没有请求完的请求了。
但是这个方法有个弊端,就是比较麻烦,每次都要手动去调用source.cance()方法。怎么做到全局统一管理呢?
官网方法二:
还可以通过传递一个 executor 函数到 CancelToken 的构造函数来创建 cancel token:
var CancelToken = axios.CancelToken;
var cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
cancel = c;
})
});
// 取消请求
cancel();
根据这个方法:我们一步一步来实现它:
1、在main.js里写一个全局httpRequestList的空数组,用来装我们的cancel函数:
// main.js
Vue.$httpRequestList = []
2、再在我们封装好的post,get请求里面,将每一个请求里面都做一个将cancel函数推入的httpRequestList数组的动作:这样我们的每一个请求里面,都包含了一个cancelToken对象。
POST (url, data, errMsg) {
const CancelToken = axios.CancelToken
return axios.post(url, data, {
timeout: 30000,
cancelToken: new CancelToken(function executor (c) {
Vue.$httpRequestList.push(c)
})
}).then(checkStatus).then(res => checkCode(res, errMsg))
},
GET (url, params, errMsg) {
const CancelToken = axios.CancelToken
return axios.get(url, {
params: {
_t: +(new Date()),
...params
},
timeout: 30000,
cancelToken: new CancelToken(function executor (c) {
Vue.$httpRequestList.push(c)
})
}).then(checkStatus).then(res => checkCode(res, errMsg))
}
3、在这之后我们要写一个执行cancel方法的方法:
import Vue from 'vue'
export const clearHttpRequestingList = () => {
if (Vue.$httpRequestList.length > 0) {
Vue.$httpRequestList.forEach((item) => {
//item就是之前每一个请求装进httpRequestList数组的cancel方法,item()执行后,
//如果该请求是pending状态,那么可以直接取消掉。执行完后记得清空httpRequestList数组。
item()
})
Vue.$httpRequestList = []
}
}
4、最后我们回到main.js,在每次跳转之前执行clearHttpRequestingList()函数。
router.beforeEach((to, from, next) => {
clearHttpRequestingList()
..........这下面是你的路由验证代码..........
})
这样就实现了每次路由跳转之前,就清空之前出于pending状态的请求,优化了性能。
总结
觉得写得好的,对你有帮助的,可以分享给身边人,知识越分享越多,千万不要吝啬呀
后续更新其它前端小知识总结,请关注我,整理好,分享给你们,我们一起学前端