Skip to content

网络

  • 预连接
    • <link rel="preconnect" href="https://cdn.domain.com">
    • <link rel="dns-prefetch" href="https://cdn.domain.com">
  • 预加载
    • <link rel="preload" href="http://example.com/xxx.js" />
    • <link rel="prefetch" href="http://example.com/xxx.js" />
  • 预渲染
    • <link rel="prerender" href="http://example.com/xxx" />
  • CDN
  • 域名收敛
  • 使用 HTTP/2.0
  • 资源优化
    • 体积优化(压缩、移除冗余代码、...)
    • 加载优化(分包、按需加载、...)

我们先看一张图:

网络请求耗时分析

这是本站首页的网络请求耗时,着重看三个时间指标:

  • Total Connection Time: 整体的连接耗时
  • TTFB (Time to First Byte): 首字节传输耗时
  • Content Download: 内容传输耗时

预连接

在慢速网络中建立连接通常需要消耗大量时间,尤其是在涉及安全连接时,因为它可能涉及到 DNS 查找、重定向以及用于处理用户请求而与最终服务器的多次往返。可以通过 preconnect, dns-prefetch 优化连接耗时。比如:

html
<link rel="preconnect" href="https://cdn.domain.com" crossorigin>
<link rel="dns-prefetch" href="https://cdn.domain.com">

几个注意点:

  • dns-prefetch 仅对跨域的 DNS 查找有效,因此请避免使用它来指向你的站点或域。这是因为,到浏览器解析到该标签时,站点域背后的 IP 已经被解析
  • 还可以通过使用 HTTP 链接字段将 dns-prefetch(以及其他资源提示)指定为 HTTP 标头
    http
    Link: <https://fonts.gstatic.com/>; rel=dns-prefetch
  • 可以将 dns-prefetchpreconnect 一起使用,dns-prefetch 仅执行 DNS 查找,但 preconnect 会建立与服务器的连接,如果站点是通过 HTTPS 服务的,则此过程包括 DNS 解析,建立 TCP 连接以及执行 TLS 握手。将两者结合起来可以进一步减少跨域请求的延迟感知
  • 对于 CDN 的资源,由于缓存的存在大多数情况并不会发送资源请求,预连接反而会浪费资源

TIP

  • 浏览器对于 dns-prefetch 的支持比对 preconnect 的支持要好
  • 如果页面需要建立与许多第三方域的连接,若将它们全部 preconnect 可能会适得其反。 preconnect 提示最好仅用于最关键的连接。对于其他的,只需使用 <link rel="dns-prefetch"> 即可节省第一步的时间 - DNS 查找
  • 一些资源(如字体)以匿名模式加载。在这种情况下,应给预连接增加 crossorigin 属性。如果省略它,则浏览器将仅执行 DNS 查找。

预加载

可以通过 preload, prefetch 减少内容传输耗时,优化首屏时间。

preload

preload 是告诉浏览器预先请求当前页面需要的资源(关键的脚本,字体,主要图片等)。当资源真正被使用的时候立即执行,就无需等待网络的消耗。它可以通过 Link 标签进行创建:

html
<!-- 使用 link 标签静态标记需要预加载的资源 -->
<link rel="preload" href="/path/to/style.css" as="style">

<!-- 或使用脚本动态创建一个 link 标签后插入到 head 头部 -->
<script>
    const link = document.createElement('link');
    link.rel = 'preload';
    link.as = 'style';
    link.href = '/path/to/style.css';
    document.head.appendChild(link);
</script>

当浏览器解析到这行代码就会去加载 href 中对应的资源但不执行,待到真正使用到的时候再执行,另一种方式方式就是在 HTTP 响应头中加上 preload 字段:

http
Link: <https://example.com/other/styles.css>; rel=preload; as=style

这种方式比通过 Link 响应头方式加载资源方式更快,请求在返回还没到解析页面的时候就已经开始预加载资源了。

prefetch

prefetchpreload 不同,它的作用是告诉浏览器未来可能会使用到的某个资源,浏览器就会在闲时去加载对应的资源。用法跟 preload 一样:

Html 模式:

html
<link rel="prefetch" href="/path/to/style.css" as="style">

HTTP 响应头模式:

http
Link: <https://example.com/other/styles.css>; rel=prefetch; as=style

TIP

  • 当一个资源被 preload 或者 prefetch 获取后,它将被放在内存缓存中等待被使用,如果资源存在有效的缓存机制(如 cache-control 或 max-age),它将被存储在 HTTP 缓存中,可以被不同页面所使用
  • 正确使用 preload / prefetch 不会造成二次下载,也就说:当页面上使用到这个资源时候 preload 资源还没下载完,这时候不会造成二次下载,会等待第一次下载并执行脚本
  • 对于 preload 来说,一旦页面关闭了,它就会立即停止 preload 获取资源,而对于 prefetch 资源,即使页面关闭,prefetch 发起的请求仍会进行不会中断
  • 没有用到的 preload 资源在 Chrome 的 console 里会在 onload 事件 3s 后发生警告
  • preload 是告诉浏览器页面必定需要的资源,浏览器一定会加载这些资源,而 prefetch 是告诉浏览器页面可能需要的资源,浏览器不一定会加载这些资源。没有用到的 preload 资源在 Chrome 的 console 里会在 onload 事件 3s 后发生警告。建议对于当前页面很有必要的资源使用 preload,对于可能在将来的页面中使用的资源使用 prefetch

什么情况会导致二次获取

  • 对于同一个资源同时使用 preloadprefetch 会造成二次的下载
  • preload 字体不带 crossorigin 也将会二次获取

是否会对用户的带宽造成浪费

preloadprefetch 情况下,如果资源不能被缓存,那么都有可能浪费一部分带宽,在移动端请慎用。

如何检测 preload 支持情况

js
const preloadSupported = () => {
    const link = document.createElement('link');
    const relList = link.relList;
    if (!relList || !relList.supports) return false;
    return relList.supports('preload');
};

资源优化

通过优化资源,可以减少网络传输带来的损耗。以图片资源为例,我们通常可以做以下处理:

  1. 不用图片,很多时候会使用到很多修饰类图片,其实这类修饰图片完全可以用 CSS 去代替
  2. 使用适宜的尺寸,对于移动端来说,屏幕宽度就那么点,完全没有必要去加载原图浪费带宽
  3. 小图使用 base64 格式
  4. 将多个图标文件整合到一张图片中(雪碧图)
  5. 选择正确的图片格式:
    • 对于能够显示 WebP 格式的浏览器尽量使用 WebP 格式。因为 WebP 格式具有更好的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量,缺点就是兼容性并不好
    • 小图使用 PNG,其实对于大部分图标这类图片,完全可以使用 SVG 代替
    • 照片使用 JPEG

缓存

  • HTTP 缓存
    • keep-alive
    • 强缓存
    • 协商缓存
  • 持久化缓存
    • localStorage
    • sessionStorage
    • ...
  • ServiceWorker

渲染

  • 懒执行
  • 懒加载
  • 多线程(Web Worker)
  • 渲染性能
    • 渲染过程:样式表放在 head 内;不用使用 css 表达式;使用 link 代替 @import;script 放页面底部;减少 DOM 操作
    • 事件代理
    • 防抖和节流
    • 减少回流和重绘
    • 利用 GPU 加速
    • 服务端渲染

懒执行

懒执行就是将某些逻辑延迟到使用时再计算。该技术可以用于首屏优化,对于某些耗时逻辑并不需要在首屏就使用的,就可以使用懒执行。懒执行需要唤醒,一般可以通过定时器、观察器或者事件的调用来唤醒。

举个例子:稿定 H5 编辑器输出的多页作品,每个画布上的元素动画都是等待该画布进入视口后才开始执行。

懒加载

懒加载就是将不关键的资源延后加载。

懒加载的原理就是只加载自定义区域(通常是可视区域,但也可以是即将进入可视区域)内需要加载的东西。对于图片来说,先设置图片标签的 src 属性为一张占位图,将真实的图片资源放入一个自定义属性中,当进入自定义区域时,就将自定义属性替换为 src 属性,这样图片就会去下载资源,实现了图片懒加载。

懒加载不仅可以用于图片,也可以使用在别的资源上。比如进入可视区域才开始播放视频等等。

多线程

JavaScript 语言采用的是单线程模型,也就是说,所有任务只能在一个线程上完成,一次只能做一件事。前面的任务没做完,后面的任务只能等着。随着电脑计算能力的增强,尤其是多核 CPU 的出现,单线程带来很大的不便,无法充分发挥计算机的计算能力。

Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。

参考资料