本文为《前端性能优化》阅读笔记。
一、缓存
如果远程请求都不必发出,又何须优化加载性能呢?
所以,审视一下我们的应用、业务,看看哪些性能问题是可以在源头上解决的。
疑问:什么时候使用cache api,什么时候使用http缓存?
答:相较于http缓存,cache api可以纯前端掌控是否保存缓存,无需添加额外的http首部字段,利用Service Worker代理请求,挑选想要缓存的服务数据。
1、本地数据存储
- 对于一些请求,我们可以直接在业务代码侧进行缓存处理。缓存方式包括 localStorage、sessionStorage、indexedDB。
-
2、内存缓存
适用场景:当你访问一个页面及其子资源时,有时候会出现一个资源被使用多次,例如图标。
-
3、cache api
提供给了客户端构建请求缓存机制的能力。
- Cache 接口为缓存的 Request / Response 对象对提供存储机制,例如,作为ServiceWorker 生命周期的一部分,Cache 接口像 workers 一样,是暴露在 window 作用域下的。
- 通常和Service Worker配合使用。
- Service Worker有一个非常重要的特性:你可以在Service Worker中监听所有客户端(Web)发出的请求,然后通过Service Worker来代理,向后端服务发起请求。通过监听用户请求信息,Service Worker可以决定是否使用缓存来作为Web请求的返回。
想要进一步了解 Service Worker 和 Cache API 的使用可以看这篇文章[3]。同时推荐使用 Google 的 Workbox
4、http缓存
强缓存不发请求到服务器,协商缓存会发请求到服务器。
HTTP/2 的 Push 功能所带来的
- 不会在额外检查资源是否过期
- 存活时间很短,甚至短过内存缓存(例如有文章提到,Chrome 中为 5min 左右);
- 只会被使用一次;
-
二、请求
1、避免多余重定向
2、DNS预解析 访问远程服务的时候,不会直接使用服务的出口 IP,而是使用域名。
- 直接使用服务出口IP的恐怕是异端……
- DNS 解析流程可能会很长,耗时很高,所以整个 DNS 服务,包括客户端都会有缓存机制
dns-prefetch
<link rel="dns-prefetch" href="//yourwebsite.com">
3、预先建立链接
浏览器可以视情况完成部分工作。<link rel="preconnect" href="//sample.com">
4、使用CDN
对于使用 CDN 的资源,DNS 解析会将 CDN 资源的域名解析到 CDN 服务的负载均衡器上
前提是使用了DNS解析。三、服务端响应
四、页面解析与处理
解析js时使用defer 或 async属性
- defer 会在 HTML 解析完成后;而 async 则是下载完成就立即开始执行
- 推荐在一些与主业务无关的 JavaScript 脚本上使用 async。例如统计脚本、监控脚本、广告脚本等。
-
五、静态页面资源
javascript
1、减少不必要的请求
代码拆分&按需加载
- 主要目的是按需加载,代码拆分只是为按需加载服务
代码合并
使用 UglifyJS 做源码级别的压缩
- 将变量替换为短命名、去掉多余的换行符等方式。
- 基本已经成为了前端开发的标配。
- 一些文本压缩算法
- gzip
- 在响应头Content-Encoding中会体现
- 深色的数字表示压缩后的大小为 22.0KB,浅色部分表示压缩前的大小为 91.9KB
- 在 Nginx 中就包含了 ngx_http_gzip_module[3] 模块,通过简单的配置就可以开启
- Tree-Shaking
- Tree Shaking 最早进入到前端的视线主要是因为 Rollup,后来在 webpack 中也被实现了
- 本质是通过检测源码中不会被使用到的部分,将其删除,从而减小代码的体积。
- Tree Shaking 非常依赖于 ESM,都属于静态分析
- 前端流行的工具库 lodash 一般直接安装的版本是非 ESM 的,为了支持 Tree Shaking,我们需要去安装它的 ESM 版本 —— lodash-es 来实现 Tree Shaking[6]。
- 优化polyfill 的使用
- 一些程序员们开发了新特性对应的 polyfill,用于在非兼容浏览器上也能使用新特性的 API。
- 只加载真正所需的 polyfill 将会帮助你减小代码体积
webpack
- 可以通过 webpack-bundle-analyzer 这个工具来查看打包代码里面各个模块的占用大小
- 打包体积过大主要是因为引入了不合适的包
3、解析与执行
javascript解析耗时
- 删除不必要的代码,对于降低 Parse 与 Compile 的负载也是很有帮助的
- 避免long task(耗时长的javascript运行)
-
4、缓存
以下方案均基于一个前提讨论:新项目上线部署。小公司里的新项目上线部署改动大,没有做任何增量部署的考虑,属于完全没接触过的盲区。
将基础库代码打包合并
- 不会每次发布新版都会让用户花费不必要的带宽重新下载基础库。
- webapack optimization.splitChunks分离公共库
减少webpack编译不当带来的缓存失效
按需加载、请求合并、优先级
- 代码压缩:CSS uglify 工具,例如 clean-css,可以优化代码、删除多余的换行与空格。
-
编码优化
避免使用昂贵的属性
- flex 进行布局比我们用的一些“老式”方法性能更好
- 慎用@import:这会把你的请求变得串行化
简化选择器
如果使用 webpack 作为构建工具,我们一般会使用 css-loader 和 style-loader,这样可以直接在 JavaScript 代码中 import 样式文件。不过这样带来的一个问题就是样式代码其实是耦合在 JavaScript 代码中的,通过运行时添加 style 标签注入页面。
一个更好的做法是在生产环境中将样式信息单独抽离成 CSS 文件
图片
1、优化请求数
雪碧图
- 请求多个图片时,可能受到浏览器并发 HTTP 请求数的限制
- 将图标合并为一张大图可以实现「20+ → 1」的巨大缩减
- 在 webpack 中使用 webpack-spritesmith,或者在 gulp 中使用 gulp.spritesmith
- 两者都是基于于 spritesmith 这个库
懒加载
- 尽量只加载用户正在浏览或者即将会浏览到的图片
- 可以借助一些已有的工具库,例如 aFarkas/lazysizes、verlok/lazyload、tuupola/lazyload 等
- 在 CSS 中使用的图片一样可以懒加载url()
- 将图片原 url 的值替换为 base64
在网站上使用 WebP 格式
- 一些需要缩放与高保真的情况,或者用作图标的场景下,使用 SVG
- 使用静音(muted)的 video 来代替 GIF
- 使用合适的大小和分辨率
- 质量权衡
- 删除冗余的图片信息
-
3、缓存
六、运行时
1、强制同步布局
应该尽量避免直接通过javascript操作DOM,走react的渲染能更好地提升性能,因为react有一个diffing机制,会将元素和它的子元素与它们之前的状态进行比较,并只会进行必要的更新来使 DOM 达到预期的状态,实现了一个局部更新的效果。而其setState的更新队列机制,也是一个批量更新的过程。
2、长列表优化
virtual list:只渲染可见区域附近的列表元素。
-
3、避免javascript运行时间过长
运行栈中的javascript任务会占用其他任务导致卡顿
解决方案
动画相关
- 渲染合成层
-
5、滚动事件的性能优化
-
6. Passive event listeners
Passive event listeners用于阻止浏览器默认行为
其他:监控与测试
一般会把性能数据分为两种
- Lab data,例如在 CI/CD 中加入 lighthouse。
- Field data,也被成为 RUM (Real User Monitoring),是指采集线上实际的性能数据来进行监控。
- 标准化场景建议用lab data;复杂场景建议用field data