Appearance
网络
- 预连接
<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 标头httpLink: <https://fonts.gstatic.com/>; rel=dns-prefetch - 可以将
dns-prefetch与preconnect一起使用,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
prefetch 跟 preload 不同,它的作用是告诉浏览器未来可能会使用到的某个资源,浏览器就会在闲时去加载对应的资源。用法跟 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=styleTIP
- 当一个资源被
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。
什么情况会导致二次获取?
- 对于同一个资源同时使用
preload和prefetch会造成二次的下载 preload字体不带crossorigin也将会二次获取
是否会对用户的带宽造成浪费?
用 preload 和 prefetch 情况下,如果资源不能被缓存,那么都有可能浪费一部分带宽,在移动端请慎用。
如何检测 preload 支持情况?
js
const preloadSupported = () => {
const link = document.createElement('link');
const relList = link.relList;
if (!relList || !relList.supports) return false;
return relList.supports('preload');
};资源优化
通过优化资源,可以减少网络传输带来的损耗。以图片资源为例,我们通常可以做以下处理:
- 不用图片,很多时候会使用到很多修饰类图片,其实这类修饰图片完全可以用 CSS 去代替
- 使用适宜的尺寸,对于移动端来说,屏幕宽度就那么点,完全没有必要去加载原图浪费带宽
- 小图使用 base64 格式
- 将多个图标文件整合到一张图片中(雪碧图)
- 选择正确的图片格式:
- 对于能够显示 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 交互)就会很流畅,不会被阻塞或拖慢。
