方向
- 减少页面体积,提升网络加载
- 优化页面渲染
- 提高(开发环境)编译的速度
必说
- 静态资源使用强缓存、协商缓存,并使用 CDN
- webpack code split 、使用 es module 形式组件或者使用
babel-plugin-import按需加载组件开启 tree shaking - 资源懒加载,比如使用 vue-lazyLoader
- 虚拟列表
- HTTP2,多路复用、首部压缩、多请求并发等
- 使用服务端渲染优化首屏加载速度
- 代码层面,事件节流、防抖,事件代理;合理使用 requestAnimationFrame 动画代替 setTimeout
- 优化 webpack 的开发速度,使用 vite
性能优化
标准
- 尽量减少 HTTP 请求数。比如合并接口,使用 url-loader 将小的图片处理成 base64 到 HTML 中
- 开启 DNS 预解析,使用 CDN 静态资源服务器。
<link rel="dns-prefetch" href="//yiliang.site"> - 使用强缓存、协商缓存
- 使用非阻塞方式下载 js 脚本, 异步加载等。
- 动态脚本加载: 使用 JS 创建一个 script 标签再插入到页面中
- defer(IE):整个 HTML解析完后才会执行,如果是多个,按照加载顺序依次执行
- async: 加载完之后立即执行,如果是多个,执行和加载顺序无关
- webpack code split 进行切割 bundle 文件,使用 ES6 模块来开启 tree shaking,组件库提供 es module 形式按需加载等。
- 合并 DOM 插入,DOM 操作是非常耗费性能的,因此插入多个标签时,先插入 Fragment 然后再统一插入 DOM。
- 事件节流、防抖
- 删减 cookie, 静态文件额外申请一个不包含 cookie 的域。
- 服务器开启 Gzip 等压缩。差不多能压缩 1/4 的体积。
- 合理使用 requestAnimationFrame 动画代替 setTimeout
- 使用 Web Workers
- 使用 HTTP2
- 选用合适的图片格式 webp 最合适 ?,图片懒加载、组件懒加载、请求懒加载
- list 海量数据,虚拟列表
如何实施
性能优化的标准中提到的都是单个点,性能优化项目具体实施起来,应该按照下面步骤推进:
- 建立性能数据收集平台,摸底当前性能数据,通过性能打点,将上述整个页面打开过 程消耗时间记录下来
- 分析耗时较长时间段原因,寻找优化点,确定优化目标
- 开始优化
- 通过数据收集平台记录优化效果
- 不断调整优化点和预期目标,循环 2~4 步骤
TODO: 首屏时间和白屏时间
Performance 接口可以获取到当前页面中与性能相关的信息。 该类型的对象可以通过调用只读属性 Window.performance 来获得。
白屏时间:
performance.timing.responseStart - performance.timing.navigationStart;
首屏时间
window.onload = () => {
new Date() - performance.timing.responseStart;
};
如何改善首屏优化和白屏
- 路由懒加载组件懒加载等。
- 骨架屏效果。
- 首屏请求的接口的优化?
- SSR
总结
- cdn 分发(减少传输距离)。通过在多台服务器部署相同的副本,当用户访问时,服务器根据用户跟哪台服务器距离近,来决定哪台服务器去响应这个请求。
- 后端在业务层的缓存。数据库查询缓存是可以设置缓存的,这个对于处于高频率的请求很有用。浏览器一般不会对 content-type: application/json;的接口进行缓存,所以有时需要我们手动地为接口设置缓存。比如一个用户的签到状态,它的缓存时间可以设置到明天之前。
- 静态文件缓存方案。这个最常看到。现在流行的方式是文件 hash+强缓存的一个方案。比如 hash+ cache control: max-age=1 年。
- 前端的资源动态加载:
- 路由动态加载,最常用的做法,以页面为单位,进行动态加载。
- 组件动态加载(offScreen Component),对于不在当前视窗的组件,先不加载。
- 图片懒加载(offScreen Image),同上。值得庆幸的是,越来越多的浏览器支持原生的懒加载,通过给 img 标签加上 loading="lazy 来开启懒加载模式。
- 减少请求的数量。这点在 http1.1 的优势很明显,因为 http1.1 的请求是串行的(尽管有多个 tcp 通道),每个请求都需要往返后才能继续下个请求。此时合并请求可以减少在路途上浪费的时间,此外还会带来重复的请求头部信息(比如 cookie)。在 http2.0 中这个问题会弱化很多,但也有做的必要。
- 页面使用骨架屏。意思是在首屏加载完成之前,通过渲染一些简单元素进行占位。骨架屏的好处在于可以减少用户等待时的急躁情绪。这点很有效,在很多成熟的网站(京东、淘宝、Youtube)都有大量应用。没有骨架屏的话,一个 loading 的菊花图也是可以的。
- 使用 ssr 渲染。
- 引入 http2.0。http2.0 对比 http1.1,最主要的提升是传输性能,在接口小而多的时候会更加明显。
- 利用好 http 压缩。即使是最普通的 gzip,也能把 bootstrap.min.css 压缩到原来的 17%。可见,压缩的效果非常明显,特别是对于文本类的静态资源。另外,接口也是能压缩的。接口不大的话不用压缩,因为性价比低(考虑压缩和解压的时间)。
- 利用好 script 标签的 async 和 defer 这两个属性。功能独立且不要求马上执行的 js 文件,可以加入 async 属性。如果是优先级低且没有依赖的 js,可以加入 defer 属性。
首屏和白屏时间如何计算
首屏时间的计算,可以由 Native WebView 提供的类似 onload 的方法实现,在 ios 下对应的是 webViewDidFinishLoad,在 android 下对应的是 onPageFinished 事件。 白屏的定义有多种。可以认为“没有任何内容”是白屏,可以认为“网络或服务异常”是白屏,可以认为“数据加载中”是白屏,可以认为“图片加载不出来”是白屏。场景不同,白屏的计算方式就不相同。 方法 1:当页面的元素数小于 x 时,则认为页面白屏。比如“没有任何内容”,可以获取页面的 DOM 节点数,判断 DOM 节点数少于某个阈值 X,则认为白屏。 方法 2:当页面出现业务定义的错误码时,则认为是白屏。比如“网络或服务异常”。 方法 3:当页面出现业务定义的特征值时,则认为是白屏。比如“数据加载中”。
用户页面遇到白屏问题,解决问题的思路(场景题)
- 白屏时间节点指的是从用户进入网站(输入 url、刷新、跳转等方式)的时刻开始计算,一直到页面有内容展示出来的时间节点。这个过程包括 dns 查询、建立 tcp 连接、发送首个 http 请求(如果使用 https 还要介入 TLS 的验证时间)、返回 html 文档、html 文档 head 解析完毕。
- 查看监控系统,有误错误日志上报。
- 模拟该用户,(所有接口都会被替换身份),排查是否是接口原因。
- 从监控日志中或者直接问用户,捞用户的机型(移动端)和浏览器版本,排查是否是某个 api 有兼容性问题导致的。
- 最好能够有一致型号的手机,复现。 或者给一个测试的链接,包含 vConsole 和全量日志上报的版本,让其访问,看是否表现一致。
- 用过仅有他的手机,且仅有某一个环境能复现,那就只能麻烦它再请求一次,观察一下实时日志,看实在 cdn 阶段 or nginx 阶段 or 服务阶段出现的问题。如果自己能复现,则自己排查。
指标
- DAU: 日活跃用户数量(Daily Active User)。
- PV 访问量(Page View),即页面访问量,每打开一次页面 PV 计数+1,刷新页面也是。
- UV 访问数(Unique Visitor)指独立访客访问数,一台电脑终端为一个访客。
- 白屏时间:从浏览器输入地址并回车后到页面开始有内容的时间;
- 首屏时间:从浏览器输入地址并回车后到首屏内容渲染完毕的时间;首屏时间 = 白屏时间 + 首屏渲染时间
- 用户可操作时间节点:domready 触发节点,点击事件有反应;
- 总下载时间:window.onload 的触发节点。
Performance API
- memory 内存
- navigation 导航相关,包括重定向次数等
- timing 对象:包含了网络、解析等一系列的时间数据。提供浏览器处理网页各个阶段的耗时。
- ...
- connectStart
- connectEnd
- loadEventStart 当前网页 load 事件的回调函数开始时的 Unix 毫秒时间戳。
- loadEventEnd 当前网页 load 事件的回调函数运行结束时的 Unix 毫秒时间戳。
- getEntries 浏览器获取网页时,会对网页中每一个对象(脚本文件、样式表、图片文件等等)发出一个 HTTP 请求。performance.getEntries 方法以数组形式,返回这些请求的时间统计信息,有多少个请求,返回数组就会有多少个成员。
https://juejin.cn/post/6844903651262070791
关键指标计算
- DNS 查询耗时 = domainLookupEnd - domainLookupStart
- TCP 链接耗时 = connectEnd - connectStart
- request 请求耗时 = responseEnd - responseStart
- 解析 dom 树耗时 = domComplete - domInteractive
- 白屏时间 = domloadng - fetchStart
- domready 时间 = domContentLoadedEventEnd - fetchStart
- onload 时间 = loadEventEnd - fetchStart