一般情况下,衡量web应用性能主要基于两个方面,加载时优化(LCP、FID、FP、FCP等指标)
、运行时优化(代码层面)
;
在实际应用场景中,这会受到应用所使用的前端架构的影响,所以优化性能的第一步是为你的应用类型确定合适的架构
这里记录一些较为通用的优化手段
加载优化
从一个http请求发生(从输入URL到页面加载的全过程)的详细过程中发掘一些可优化点
指标:首屏优化,提升首屏加载速度
1、减少http请求数量、请求大小
- 图片使用精灵图(sprite),减少http请求数量
- 使用SVG或者字体图标(矢量图,放大不会失真,渲染速度快), 使用png格式图片(无损压缩, PNG-8:256 种颜),小icon图标转成base64编码,图片压缩
- 压缩文件减少http请求大小(例如使用HtmlWebpackPlugin压缩html, 使用UglifyPlugin压缩js)
- 合并文件减少http请求数量, 使用webpack插件提取公共代码
2、CDN加载静态资源
缓存源站的资源到各cdn节点上,用户就近获取资源,缓解服务器压力,提升响应速度
3、按需加载,减少冗余代码
(1) 图片懒加载
核心思路:监听页面滚动事件,图片出现在可视区域内则进行加载显示
// 原理(通过 html5 自定义属性 data-xxx 先暂存 src 的值,
// 然后在图片出现在屏幕可视区域的时候,再将 data-xxx 的值重新赋值到 img 的 src 属性)
<img src="" data-src="xxxxx">
// 监听滚动事件
img.src = img.getAttribute("data-src")
(2) 路由懒加载
实现:webpackChunkName命名chunk将代码进行分割, 结合 import()动态导入
require()异步,import()同步
最终实现效果:首页资源压缩
(3) 组件懒加载
实现:原理同路由懒加载
示例:dialog、Modal
最终实现效果:首页资源压缩
(4) 减少babel转译过程中的冗余代码
使用@babel/plugin-transform-runtime 按需引入@babel/runtime中的辅助函数(helper)
4、服务端渲染SSR
服务端解析html内容, 浏览器端直接渲染
实现方式: (1) JSP (2) vue + nuxt (3) express + react
与客户端渲染CSR的区别: SSR利于首屏渲染、SEO优化;CSR适用于交互性强、不需要SEO的项目 部分场景混合搭配使用,例如next.js
5、css && js
(1) js文件加载
- async: 无序、异步
- defer: 按引入顺序加载,异步;实际应用于控制资源加载顺序
- module
<link rel="preload" />
: 提前加载关键资源<link rel="prefetch" />
: 用于加载其他页面(非首页)所需要的资源,以便加快后续页面的打开速度
(2) css
- 内联部分css(例如首屏关键css), 使渲染时间提前
- 异步加载外部样式
// 设置 media: print 或 rel: alternate stylesheet实现异步加载
<link rel="stylesheet" href="mystyles.css" media="print" onload="this.media='all'">
<link rel="alternate stylesheet" href="mystyles.css" onload="this.rel='stylesheet'">
// 或, 此方式浏览器支持不友好
<link rel="preload" href="mystyles.css" as="style">
- link 替代 @import, 避免!Important @import会影响文件并行下载
6. 打包优化
- 使用构建工具时,选择提供es模块格式的依赖(tree-shaking更友好)
- 第三方依赖按需引入
- vue3函数式编程(利用tree-shake精简项目)
- 合理利用tree-shaking:基于es6模块静态分析,消除无用代码,减小打包体积
示例:平时编写代码时例如工具函数,使用export分别导出,打包时未使用的函数将被丢弃;
// export default 导出的是一个对象,**无法通过静态分析判断出一个对象的哪些变量未被使用
// tree-shaking 只对使用 export 导出的变量生效**
运行优化(代码优化)
1. js
- 函数防抖、节流等
> 两种实现方式:定时器和时间戳
1. 函数防抖
> 事件触发后经过一段时间触发响应,这段时间内若再次触发则重新计时, 即"只有最后一次操作被触发",
> 适用于处理最终结果
function debounce(fn, delay) {
let timerId = null;
return function (...args) {
timerId && clearTimeout(timerId);
timerId = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
const callback = function (e, data1) {
console.log(e, data1);
};
document.addEventListener('mousemove', debounce(callback, 3000));
应用场景:
- 输入框搜索
- 输入框输入验证
- 浏览器窗口resize
2. 函数节流
> 控制函数的调用频率(不大于给定的ms延迟时间),即指定时间间隔内,只执行一次
function throttle(fn, delay) {
let timerId = null;
return function (...args) {
if (!timerId) {
timerId = setTimeout(() => {
timerId = null;
fn.apply(this, args);
}, delay);
}
};
}
const callback = function (e, data1) {
console.log(e, data1);
};
document.addEventListener('mousemove', throttle(callback, 3000));
应用场景:
- 长列表滚动加载
- 频繁点击事件(点赞、表单提交等操作)
- 输入框自动补全
- 图片懒加载
- 尾调用优化
- 清除定时器、事件监听
- 引用类型手动删除引用,释放内存, 也可使用WeakSet、WeakMap结构(弱引用,不计入垃圾回收机制, 可用于解决拷贝环问题)
2. 减少重排和重绘
回流触发
- 添加/删除dom元素
- 元素布局位置变化
- 元素尺寸变化(内外边距、边框、宽高)
- 内容变化
- 页面初始渲染
- 浏览器窗口resize
- 一些特定属性值的获取(offsetTop、scrollWidth、clientLeft...., 这些属性涉及即时计算)
- getComputedStyle
重绘触发
- 回流
- 元素文本颜色、阴影等属性修改
浏览器优化:队列化修改并批量执行来优化重排过程
避免/优化回流方式
- 避免设置多项内联样式
- 避免使用
table
布局 - 对于复杂动画,设置绝对定位/固定定位使其脱离文档流
- 使用css3硬件加速,可以让
transform
、opacity
、filters
这些动画不会引起回流重绘 JavaScript
动态插入多个节点时, 使用DocumentFragment
特别地:
// 使用类名合并样式
<style>
.basic_style {
width: 100px;
height: 200px;
border: 10px solid red;
color: red;
}
</style>
<script>
const container = document.getElementById('container')
container.classList.add('basic_style')
</script>
// 离线操作(适用于频繁的dom操作)
let container = document.getElementById('container')
container.style.display = 'none'
container.style.width = '100px'
container.style.border = '10px solid red'
container.style.color = 'red'
// ...(省略了类似的后续操作)
container.style.display = 'block'
3. css优化
- css实现渐变、特效
- 属性继承,避免重复编写
- 避免高消耗属性,渲染速度慢(box-shadow、border-radius、position: fixed、transform、:nth-child、filter...)
- 简化选择器,降低选择器的复杂性,减少匹配时间
a. 避免使用过多嵌套层级、
b. 使用id选择器时尽量避免嵌套、
c. 通配符和属性选择器效率最低,避免使用
4. 长列表虚拟滚动
实现:只渲染可视区域的列表项;计算列表总高度,滚动时更新startIndex、endIndex从列表中截取所需数据
最终实现效果:减少首页加载时间,增强交互体验
5. requestAnimationFrame 制作动画
可以解决用 setTimeout/setInterval 制作动画卡顿的情况
setTimeout/setInterval、requestAnimationFrame 的区别:
(1) 引擎层面
setTimeout/setInterval 属于 JS引擎
,requestAnimationFrame 属于 GUI引擎
,
JS引擎与GUI引擎
是互斥的
(2) 时间是否准确
requestAnimationFrame 刷新频率是固定且准确的,但 setTimeout/setInterval 是宏任务,根据事件轮询机制,其他任务会阻塞或延迟js任务的执行,会出现定时器不准的情况
(3) 性能层面
当页面被隐藏或最小化时,setTimeout/setInterval 定时器仍会在后台执行动画任务,而 requestAnimationFrame 当页面处于未激活的状态下,屏幕刷新任务会暂停
最终实现效果:运行时优化,减少页面卡顿、掉帧