性能优化的目的
提高用户体验,保证用户留存
- 首屏时间(白屏时间)
- 首次可交互时间
- 首次有意义内容渲染时间
- 卡顿
- loading 时间
I/O 优化
前端常见 I/O 是网络 I/O
HTTP 协议升级
合理利用缓存
- CDN:内容分发网络,源站内容分发至全国(全球)不同节点
- CDN 预热:主动从源站推送到 CDN,让用户访问到 CDN 时不用回客户的源站命中
- CDN 刷新:淘汰 CDN 节点上的旧文件,重新获取文件的新版本
- 强缓存和协商缓存
只请求当前需要的资源
- 异步加载:如路由懒加载
- 懒加载:如图片懒加载
polyfill
:用来为旧浏览器提供它没有原生支持的较新的功能(API)- 打包多份,根据浏览器环境选择合适的
polyfill
包,而不是全部都加载polyfill
- 打包多份,根据浏览器环境选择合适的
缩减资源体积和请求数量
- 打包压缩:代码压缩混淆、
tree shake
等 - gzip:压缩效率极高
- 图片格式的优化:webp、压缩,根据需求选择合适的分辨率(根据屏幕分辨率展示不同分辨率的图片)
- 尽量控制
cookie
的大小:每次请求的请求头中都会携带同域下所有的cookie
- 雪碧图(HTTP/2 不需要控制请求数量,因为没有 6 个 TCP 连接限制)
时序优化:资源加载顺序
- JS 的
Promise.all
:并行执行异步任务 - SSR:把打包放到服务端,服务端对共性资源可以缓存,利于 SEO
prefetch
、prerender
、preload
,资源提示关键词
符合人体工学的设计
添加 loading、骨架屏设计,而不是白屏,降低用户焦虑度
CPU 优化
JS 长任务阻塞页面渲染
JS 执行和页面渲染都在渲染主线程一个线程中执行,JS 占用太长时间超出一帧的时间(16.667 ms),用户就会感知卡顿
解决方法:将长任务打断成多个短任务
// 下一个宏任务再继续运行
function sleep(time) {
return new Promise(resolve => setTimeout(resolve, time))
}
如何确定哪段代码执行时间长?
- Chrome 开发者工具的火焰图
- 函数计时装饰器:
export function measure(target: any, name: string, descriptor: any) { const oldValue = descriptor.value descriptor.value = async function () { console.time(name) const ret = await oldValue.apply(this, arguments) console.timeEnd(name) return ret } return descriptor }
多线程
使用 Web Worker 创建多线程运行
JS 动画使用 requestAnimationFrame
requestAnimationFrame
每帧调用,保证动画流畅性
任务优先级
符合人体工学,区分任务优先级,优先执行用户关心度和感知度高的任务
开发体验/工具链优化
dev server
- HotModuleReplacement(HMR,热模块替换):只对修改的模块重新打包编译
- OneOf:打包时每个文件都会经过所有 loader 处理,虽然因为
test
正则没匹配上,但是都要过一遍,比较慢。实际上只需要匹配一个 loader,剩下的就不匹配了
其他
- 工程化、自动化
- 代码热更新
- TS 代码提示
- 代码编辑器自动格式化、风格和错误检查