# 缩短资源传输距离

缩短资源的传输距离,最重要的是把资源存储在距离⽤户更近的地⽅。缓存,就是⼀种最常见的⽅案。在 WEB 系统中,缓存在不同环境

都有相关的实现。浏览器侧、APP 侧、CDN 侧,甚⾄后端服务器上,都可以做缓存。这⾥主要讨论服务端之外的缓存。缓存能很⼤地提

升系统性能,但相应地带来了额外的存储开销。同时,如何更新缓存,也是⼀个重要 的问题。

常见方案

  1. 配置 HTTP 缓存

    • 原理:让资源从浏览器的 http 缓存中就近获取。HTTP 缓存是 Http 协议的标准功能,所有浏览器都会遵循。

    • 适用资源:静态资源

    • 实现方式:给资源添加 cache-control 响应 header,指定 max-age。推荐的⽅式是给静态资源设置长效缓存(设置⼀个极长的

      本地缓存时间)+ ⾮覆盖式更新(每次发布会改变资源的 url 以使缓存失效 ) 的⽅式。以最⼤限度利⽤本地缓存的效果

  2. 使用 app 端缓存能力

    • 原理: 让资源从⽤户端的离线缓存中就近获取
    • 适用资源:app 中的页⾯使⽤到的静态资源
    • 实现方式:使⽤端侧提供的离线包能⼒。离线包的缓存相对⽐普通 http 缓存更稳定,不容易被系统回收。(注意离线包更新问题)
  3. 使用 CDN 缓存

    • 原理:让资源从 cdn 节点中就近获取

    • 适用资源:静态资源

    • 实现方式: 将静态资源上传⾄ cdn 源站,通过 cdn 节点和对应的域名来访问静态资源,并配置

      cdn 对资源的缓存时间。(注意覆盖式更新问题)

  4. 使 ⽤ ServiceWorker + Cache

    • 原理:Service Worker 可以在浏览器侧实现所有请求的⽹络拦截与响应,并结合 Cache Api ,以将任何 http 请求进⾏缓存。利⽤ ServiceWorker 的缓存还可以实现页⾯可 离线访问 的效果。即⽹络断开情况下,所能返回缓存中的内容。相⽐于 Http

      Cache,Service Worker + Cache ,能更灵活地控制各个 http 请求在浏览器上的缓存 策略。

    • 适用资源:所有

    • 实现方式:PWA

# 减小资源开始加载时间

要减少资源开始加载的时间,主要是通过各种预加载(即提前加载)的手段。不管是静态资源,还是动态的数据接口,都有相应的方案,可以把请求提前。预加载的方案,要用到关键资源(即影响页面核心内容和功能的资源)上。核心资源一般是即将访问的页面的主 js、css,或者页面的主数据接口。

常见方案

  1. 静态资源预加载

    • 原理:让浏览器提前发起页面面后续会用到的静态资源请求

    • 适用资源:静态资源

    • 实现方式:通过 html 的 prefetch link 标签

      ​ 通过 html 的 preload link 标签

    • 实现参考:可控静态资源预加载(后续文章会详细说明这种方案)

  2. 动态资源预加载

    • 原理:让浏览器提前发起页面核心的动态资源请求。(注意接口需要的参数、登陆态,在请求时机提前后是否仍然可以获取到)

    • 适用资源:动态资源(数据接口)

    • 实现方式:

      窗口侧提供预加载能力,在 webview 加载时就发起接口请求,并通过 bridge 将请求结果提供给页面的 js 消费。

      在 html 尽量靠前的位置,发起页面主接口请求,可以通过全局 promise 变量的形式给页面后续的功能消费。

    • 实现参考:

      通过将数据请求,前置到主 js 前,可以让 js 的加载和执行,与接口请求并行起来,从而提升页面性能

      接口提前请求

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      <html>
      <body>
      <script>
      // 在html尽量靠前部分发起数据请求
      // 如果接口需要参数或者登陆状态,需要根据实际情况去获取
      // 如果预加载多个接口,需要为不同接口设置不同的标识,以提供给后续消费
      window._preload_promise_ = fetch('/data.json');
      </script>
      <script src="main.js"></script>
      </body>
      </html>

      消费提前请求的接口

      1
      2
      3
      4
      5
      6
      // main.js
      // 要请求接口时,如果有预加载请求promise,就直接使用。否则发起数据请求
      const dataResult = window._preload_promise_ ? window._preload_promise_ : fetch('/data.json');
      dataResult.then(data => {
      // 数据处理与展示
      })

# 减少资源传输体积

随着浏览器能力的增强,业务功能的丰富,以及各种前端框架的发展,一个 WEB 页面加载的资源提及逐渐膨胀。减少资源的体积,除了可以优化页面的性能之外,对于移动端的页面,还能节省用户的流量。

各种资源都有体积优化的空间。前端工程师,通常关注的是编译后的 bundle 的体积,通过构建优化和各种压缩技术,减少构建后的 js、css 体积。除此之外,图片的体积、接口数据体积,也影响着页面的性能。针对不同资源类型,有着不同的体积优化方案。

常见方案

  1. 使用合适的方式压缩

    • 原理:通过压缩算法减少资源传输体积

    • 适用资源:所有资源

    • 实现方式:

      文本类型资源:gzip、br

      图片类型资源:webp(对浏览器兼容性有要求)

    • 实现参考:

      gzip 压缩

  2. 构建优化静态资源体积(webpack)

    • 原理:通过构建工具优化静态资源体积
    • 适用资源:静态资源
    • 实现方式:
  3. 精简动态资源内容

    • 原理:减少动态内容中不必要的内容或字段
    • 适用资源:动态资源
    • 实现方式:

# 加快网络传输速度

⽹络速度,或许是性能环节中最不可控的⼀环。⽤户与服务器之间的⽹络速度,我们很难测量和⼲涉。但考虑到 http 的传输,不仅是物理链路的数据传输,还有各种⽹络协议参与其中。⽐如 dns、tcp、https 等。我们可以从协议侧⼊⼿,寻找有哪些加快 http 传输的⽅案。

另外,cdn 也是⼀个加速⽹络的途径。虽然 cdn 通常⽤于缓存静态资源,但也可以对动态资源进⾏⽹络加速(不缓存)。

常见方案

  1. 减少 DNS 传输时间
    • 原理:减少 http 请求时,必须要执⾏的 dns 解析时间
    • 适用资源:所有
    • 实现方式:
    • 实现参考:域名预解析、预建联、连接复用
  2. 减少 TCP/https 建连时间
    • 原理:http 是基于 tcp 协议的,在⾸次建⽴连接时⽐较耗费时间,再加上 https 还要进⾏ ssl 握⼿,整个建连时间会更长,减少 tcp/https 的建连时间能有效提升⽹络传输速度,尤其对于⾸次访问。
    • 适用资源:所有
    • 实现方式:
  3. 使⽤ cdn 动态加速(全站加速)
    • 原理:cdn 动态加速,可利⽤ cdn 节点和它们之间的⽹络,加快⽤户和中⼼化服务之间的传输速度
    • 适用资源:动态资源
    • 实现方式:使⽤ cdn 服务商的动态加速功能(付费的)
  4. 减少不必要资源传输
    • 原理:多个资源同时请求,会占⽤⽹络带宽,影响页⾯的核⼼资源下载速度,在页⾯加载过程中,优先保证页⾯呈现所需要的核⼼资源下载,不让其他暂时⽤不到的资源参与⽹络竞争
    • 适用资源:所有资源
    • 实现方式:
  5. 使⽤ http2
    • 原理:http2 相⽐于 http1 ,⽀持多路复⽤,且做了头部压缩,能从协议层加快传输速度
    • 适用资源:所有
    • 实现方式:cdn 运营商会⾃带,⾃⼰的服务,⼀般⽹关层也会⾃带。如果⾃⼰的服务没有开启 http2 ,或以联系运维同学资源如何开启

# 加快后端响应速度

后端是产出动态数据的关键方,对于需要实时展示最新内容的页面,性能的上限由后端决定。在后端也有很多种性能优化方案。这里仅列举一些常用的优化方案

常见方案

  1. 服务调用从串行改成并行
  2. 合并相似业务的接口,减少 http 请求
  3. 慢 SQL 优化,统计和分析 SQL 耗时,优化 SQL 性能
  4. 使用数据缓存,通过 memcache 或者 tair 等内存缓存,缓存查询结果,提升数据库查询性能

# 减少阻塞呈现时间

页面在渲染过程中,由于浏览器的机制,或者我们代码的逻辑,有些资源的加载和执行会影响后续其他资源。因此我们要尽量减少核心资源的阻塞点

常见方案

  1. 减少不必要的阻塞资源

    • 原理:浏览器解析和渲染 html 过程中,会受 js,css 等资源的阻塞

    • 适用资源:静态资源

    • 实现方式:

    • 实现参考:

      我们在做项目时,有时会遇到环境变量文件 env_config.js ,这个文件作为关键配置要放在主 js 前,单独作为一个 js 在 html 里引入,在本地没有可用缓存时,会阻塞主 js 的加载和执行,可以直接在构建时,将文件的内容注入到 html 中,消除这个环境配置文件的加载对页面造成的阻塞

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      // vue.config.js
      module.exports = {
      chainWebpack: (config) => {
      config.plugin('html').tap(args => {
      // 注入env变量至html
      args[0].envVariable = fs.readFileSync(`env/env_${process.env.APP_ENV}.js`, 'utf8')
      return args
      })
      }
      }

      在 html 中使用此变量

      1
      2
      3
      4
      5
      6
      7
      8
      9
      <html>
      <body>
      <script>
      // 使用构建过程中注入的环境变量内容
      <%= htmlWebpackPlugin.options.envVariable %>
      </script>
      <script src="main.js"></script>
      </body>
      </html>
  2. 服务端渲染(SSR)

    • 原理:很多页面采用浏览器渲染的模式(CSR),这种模式会使页面的呈现受 html 加载、js 加载和执行、接口加载及页面渲染几个步骤的阻塞。通过服务端渲染,直接将页面核心内容在 html 请求中一起返回,减少阻塞首屏的依赖
    • 适用资源:动态资源
    • 实现方式:

# 提前呈现部分内容

页⾯要完整地加载完所有内容,通常⽐较耗时。如果能提前呈现部分内容,可以减少⽩屏时间,减少⽤户等待焦虑

常见方案

  1. 提前展⽰⾻骼图

    • 原理:先给⽤户看⼀下⾻骼图⽽不是⽩屏,减少⽩屏时间和⽤户等待焦虑
    • 适用资源:所有页面
    • 实现方式:给页⾯设计⼀个与页⾯内容结构相似的⾻骼图,在页⾯完成内容回来前,优先展⽰⾻骼图。⾻骼图最好是不要依赖主 js 加载,只依赖 html 和及内联的少量 js , css
  2. 使⽤流式加载(bigpipe)

    • 原理: 在⼀次响应⾥,利⽤ http 的流式特性,分段返回页⾯不同模块的内容,使内容较多的页

      ⾯,能先出来某些模块内容

    • 适用资源:通过 ssr 输出的动态内容

    • 实现方式:facebook bigpipe ⽅案

  3. 提前展⽰本地快照

    • 原理:先给⽤户看⼀下之前看过的页⾯快照,减少⽩屏时间和⽤户等待焦虑

    • 适用资源:对实时性要求不⾼的动态资源

    • 实现方式:将接⼝内容甚⾄⾸屏 html ⽚断缓存⾄⽤户本地缓存(例如 localstorage),⽤户⼆次访问时,直接先读取本地缓存中的页⾯主体内容呈现给⽤户,然后再请求最新的数据替换掉快照内容(注意控制页⾯不要有明显闪动)

    • 实现参考:

      1. 接口快照:缓存上一次接口

      2. html 片段快照:缓存上一次渲染好的 html 片段

      3. 快照缓存机制

        缓存位置:localStorage

        缓存有效期:1 天