方向

  1. 减少页面体积,提升网络加载
  2. 优化页面渲染
  3. 提高(开发环境)编译的速度

必说

  1. 静态资源使用强缓存、协商缓存,并使用 CDN
  2. webpack code split 、使用 es module 形式组件或者使用 babel-plugin-import 按需加载组件开启 tree shaking
  3. 资源懒加载,比如使用 vue-lazyLoader
  4. 虚拟列表
  5. HTTP2,多路复用、首部压缩、多请求并发等
  6. 使用服务端渲染优化首屏加载速度
  7. 代码层面,事件节流、防抖,事件代理;合理使用 requestAnimationFrame 动画代替 setTimeout
  8. 优化 webpack 的开发速度,使用 vite

性能优化

标准

  1. 尽量减少 HTTP 请求数。比如合并接口,使用 url-loader 将小的图片处理成 base64 到 HTML 中
  2. 开启 DNS 预解析,使用 CDN 静态资源服务器。 <link rel="dns-prefetch" href="//yiliang.site">
  3. 使用强缓存、协商缓存
  4. 使用非阻塞方式下载 js 脚本, 异步加载等。
    1. 动态脚本加载: 使用 JS 创建一个 script 标签再插入到页面中
    2. defer(IE):整个 HTML解析完后才会执行,如果是多个,按照加载顺序依次执行
    3. async: 加载完之后立即执行,如果是多个,执行和加载顺序无关
  5. webpack code split 进行切割 bundle 文件,使用 ES6 模块来开启 tree shaking,组件库提供 es module 形式按需加载等。
  6. 合并 DOM 插入,DOM 操作是非常耗费性能的,因此插入多个标签时,先插入 Fragment 然后再统一插入 DOM。
  7. 事件节流、防抖
  8. 删减 cookie, 静态文件额外申请一个不包含 cookie 的域。
  9. 服务器开启 Gzip 等压缩。差不多能压缩 1/4 的体积。
  10. 合理使用 requestAnimationFrame 动画代替 setTimeout
  11. 使用 Web Workers
  12. 使用 HTTP2
  13. 选用合适的图片格式 webp 最合适 ?,图片懒加载、组件懒加载、请求懒加载
  14. list 海量数据,虚拟列表

如何实施

性能优化的标准中提到的都是单个点,性能优化项目具体实施起来,应该按照下面步骤推进:

  1. 建立性能数据收集平台,摸底当前性能数据,通过性能打点,将上述整个页面打开过 程消耗时间记录下来
  2. 分析耗时较长时间段原因,寻找优化点,确定优化目标
  3. 开始优化
  4. 通过数据收集平台记录优化效果
  5. 不断调整优化点和预期目标,循环 2~4 步骤

TODO: 首屏时间和白屏时间

Performance 接口可以获取到当前页面中与性能相关的信息。 该类型的对象可以通过调用只读属性 Window.performance 来获得。

白屏时间:

performance.timing.responseStart - performance.timing.navigationStart;

首屏时间

window.onload = () => {
  new Date() - performance.timing.responseStart;
};

如何改善首屏优化和白屏

  1. 路由懒加载组件懒加载等。
  2. 骨架屏效果。
  3. 首屏请求的接口的优化?
  4. SSR

总结

  1. cdn 分发(减少传输距离)。通过在多台服务器部署相同的副本,当用户访问时,服务器根据用户跟哪台服务器距离近,来决定哪台服务器去响应这个请求。
  2. 后端在业务层的缓存。数据库查询缓存是可以设置缓存的,这个对于处于高频率的请求很有用。浏览器一般不会对 content-type: application/json;的接口进行缓存,所以有时需要我们手动地为接口设置缓存。比如一个用户的签到状态,它的缓存时间可以设置到明天之前。
  3. 静态文件缓存方案。这个最常看到。现在流行的方式是文件 hash+强缓存的一个方案。比如 hash+ cache control: max-age=1 年。
  4. 前端的资源动态加载:
    1. 路由动态加载,最常用的做法,以页面为单位,进行动态加载。
    2. 组件动态加载(offScreen Component),对于不在当前视窗的组件,先不加载。
    3. 图片懒加载(offScreen Image),同上。值得庆幸的是,越来越多的浏览器支持原生的懒加载,通过给 img 标签加上 loading="lazy 来开启懒加载模式。
  5. 减少请求的数量。这点在 http1.1 的优势很明显,因为 http1.1 的请求是串行的(尽管有多个 tcp 通道),每个请求都需要往返后才能继续下个请求。此时合并请求可以减少在路途上浪费的时间,此外还会带来重复的请求头部信息(比如 cookie)。在 http2.0 中这个问题会弱化很多,但也有做的必要。
  6. 页面使用骨架屏。意思是在首屏加载完成之前,通过渲染一些简单元素进行占位。骨架屏的好处在于可以减少用户等待时的急躁情绪。这点很有效,在很多成熟的网站(京东、淘宝、Youtube)都有大量应用。没有骨架屏的话,一个 loading 的菊花图也是可以的。
  7. 使用 ssr 渲染。
  8. 引入 http2.0。http2.0 对比 http1.1,最主要的提升是传输性能,在接口小而多的时候会更加明显。
  9. 利用好 http 压缩。即使是最普通的 gzip,也能把 bootstrap.min.css 压缩到原来的 17%。可见,压缩的效果非常明显,特别是对于文本类的静态资源。另外,接口也是能压缩的。接口不大的话不用压缩,因为性价比低(考虑压缩和解压的时间)。
  10. 利用好 script 标签的 async 和 defer 这两个属性。功能独立且不要求马上执行的 js 文件,可以加入 async 属性。如果是优先级低且没有依赖的 js,可以加入 defer 属性。

首屏和白屏时间如何计算

首屏时间的计算,可以由 Native WebView 提供的类似 onload 的方法实现,在 ios 下对应的是 webViewDidFinishLoad,在 android 下对应的是 onPageFinished 事件。 白屏的定义有多种。可以认为“没有任何内容”是白屏,可以认为“网络或服务异常”是白屏,可以认为“数据加载中”是白屏,可以认为“图片加载不出来”是白屏。场景不同,白屏的计算方式就不相同。 方法 1:当页面的元素数小于 x 时,则认为页面白屏。比如“没有任何内容”,可以获取页面的 DOM 节点数,判断 DOM 节点数少于某个阈值 X,则认为白屏。 方法 2:当页面出现业务定义的错误码时,则认为是白屏。比如“网络或服务异常”。 方法 3:当页面出现业务定义的特征值时,则认为是白屏。比如“数据加载中”。

用户页面遇到白屏问题,解决问题的思路(场景题)

  1. 白屏时间节点指的是从用户进入网站(输入 url、刷新、跳转等方式)的时刻开始计算,一直到页面有内容展示出来的时间节点。这个过程包括 dns 查询、建立 tcp 连接、发送首个 http 请求(如果使用 https 还要介入 TLS 的验证时间)、返回 html 文档、html 文档 head 解析完毕。
  2. 查看监控系统,有误错误日志上报。
  3. 模拟该用户,(所有接口都会被替换身份),排查是否是接口原因。
  4. 从监控日志中或者直接问用户,捞用户的机型(移动端)和浏览器版本,排查是否是某个 api 有兼容性问题导致的。
  5. 最好能够有一致型号的手机,复现。 或者给一个测试的链接,包含 vConsole 和全量日志上报的版本,让其访问,看是否表现一致。
  6. 用过仅有他的手机,且仅有某一个环境能复现,那就只能麻烦它再请求一次,观察一下实时日志,看实在 cdn 阶段 or nginx 阶段 or 服务阶段出现的问题。如果自己能复现,则自己排查。

指标

  • DAU: 日活跃用户数量(Daily Active User)。
  • PV 访问量(Page View),即页面访问量,每打开一次页面 PV 计数+1,刷新页面也是。
  • UV 访问数(Unique Visitor)指独立访客访问数,一台电脑终端为一个访客。
  • 白屏时间:从浏览器输入地址并回车后到页面开始有内容的时间;
  • 首屏时间:从浏览器输入地址并回车后到首屏内容渲染完毕的时间;首屏时间 = 白屏时间 + 首屏渲染时间
  • 用户可操作时间节点:domready 触发节点,点击事件有反应;
  • 总下载时间:window.onload 的触发节点。

Performance API

  1. memory 内存
  2. navigation 导航相关,包括重定向次数等
  3. timing 对象:包含了网络、解析等一系列的时间数据。提供浏览器处理网页各个阶段的耗时。
    1. ...
    2. connectStart
    3. connectEnd
    4. loadEventStart 当前网页 load 事件的回调函数开始时的 Unix 毫秒时间戳。
    5. loadEventEnd 当前网页 load 事件的回调函数运行结束时的 Unix 毫秒时间戳。
  4. 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

浏览器控制台的 Performance 性能排查

Last Updated:
Contributors: yiliang114