请求和响应优化
- 核心思路:
- 更好的连接传输效率;
- 更少的请求数量;
- 更小的资源大小;
- 合适的缓存策略;
- 最佳实践:
- 减少 DNS 查找:每次主机名的解析都需要一次网络往返,从而增加了请求的延退时间,同时还会阳塞后续的请求;
- 重用 TCP 连接:尽可能的使用持久连接,以消除因 TCP 握手和慢启动导致的延迟;
- 减少 HTTP 重定向:HTTP 重定向需要额外的 DNS 查询、TCP 握手等非常耗时,最佳的重定向次数为 0;
- 压缩传输的资源:比如 Gzio、区片压缩;
- 使用缓存:比如 HTTP 缓存、CDN 缓存、Service Worker 缓存;
- 使用 CDN(内容分发网络):把数据放在离用户地理位营更近的地方,可以明显减少每次 TCP 连接的网络延迟,增大吞吐量;
- 删除没有必要请求的资源
- 在客户端緩存资源:缓存必要的应用资源,避免每次都重复请求相同的内容,例如多图片下载可以考虑使用缓存;
- 内容在传输前先压缩:传输数据之前应该先压缩应用资源,把要传输的字节减少到最小,在压缩的时候确保对每种不同的资源采用最好的压缩手段;
- 消除不必要的请求开销:减少请求的 HTTP 首部数据(比如 HTTP COokie);
- 井行处理请求和响应:请求和响应的排队都会导致延迟,可以尝试并行的处理请求和响应(利用多个 HTTP1.1 连接实现并行下载,在可能的情况下使用 HTTP 管道计数);
- 针对协议版本采取优化措施:升级到 HTTP2.0;
- 根据需要采用服务端渲染方式:这种方式可以解决 SPA 应用首屏渲染慢的问题;
- 采用预渲染的方式快速加载静态页面:页面渲染的极致性能,比较适合静态页面;
DNS 解析
概念
当浏览器从(第三方)服务器请求资源时,必须先将该跨域域名之解析为 IP 地址,然后浏览器才能发出请求,此过性称为 DNS 解析;
-
DNS 作为互联网的基础协议,其解析的速度似乎很容易被网站优化人员忽视,现在大多数新浏览器己经针对 DNS 解析进行了优化,比如 DNS 缓存;
- 典型的一次 DNS 解析再要耗费 20-120 毫秒,所花费的时间几乎可以忽略不计,但是当网站中使用的资源依赖于多个不同的域的时候,时问就会成倍的增加,从而增加了网站的加载时问;
- 比如在某些图片较多的页面中,在发起图片加载请求之前预先把域名解析好将会有至少 5% 的图片加载速度提升;
-
一般来说,在前端优化中与 DNS 有关有两点:减少 DNS 的请求次数、进行 DNS 预获取:DNS Prefetch;
减少 DNS 查找
-
域名系统(DNS)将主机名映射到P地址,就像电话薄将人们的姓名映射到他们的电话号码一样,在浏览器中输入 www.taobao.com 时,浏览器联系的 DNS 解析器将返回该服务器的 IP 地址;
- DNS 有成本,DNS 通常需要 20-120 毫秒来查找给定主机名的 IP 地址;
- 在DNS查找完成之前,浏览器无法从该主机名下载任何内容;
-
缓存 DNS 查找以提高性能:
- 这种缓存可以在由用户的 ISP 或局城网维护的特殊缓存服务器上进行,但是在个别用户的计算机上也会发生缓存;
- ENS 信息保留在操作系统的 DNS 级存中 (Microsoft Windows 上的“DMS客户端服务");
- 大多数浏览器都有自己的级存,与探作系统的金存分开;
- 只要浏览器 DNS 记录保存在自己的缓存中,它就不会对操作系统发出请求进行打扰;
-
默认情况下,浏览器缓存 DNS的时间不一样:
- Intemet Explorer 会緩存30分钟的 DNS 查找,这是由 DnsCacheTimeout 注册表设置指定的;
- Firefox 在 network.dnsCacheExpiration 配置设置的控制下缓存 DNS 查找1分钟;
- Chrome 也是1分钟;
-
当客户端的 DNS 缓存为空(对于浏览器和操作系統)时,DNS 查找的次数等于网页中唯一主机名的数目;
- 这包括在页面的 URL、图像、脚本文件、样式表、Flash 对象等中使用的主机名;
- 减少唯一主机名的数量将减少 DNS 查找的数量;
-
减少域名的数量有可能减少页面中并行下载的数量:
- 避免 DNS 查找会减少响应时间,但是减少并行下载可能会增加响应时间;
- 原则是将这些资源划分为至少两个但不超过四个域名,这将在减少DNS查找和允许高度并行下载之间取得良好的折中;
dns-prefetch
-
DNS-prefetch(DNS 预获取)是尝试在请求资源之前解析域名:
- 这可能是后面要加载的文件,也可能是用户尝试打开的链接目标;
- 域名解析和内容载入是串行的网络操作,所以这个方式能波少用户的等待时间,提升用户体验;
-
dns-prefetch 可帮助开发人员掩盖 DNS 解析延迟,HTML 元素通过 dns-prefetch 的 rel 属性值提供此功能,然后在 href 用性中指要跨域的域名:
<link rel="dns-prefetch" href="https://fonts.googleapis.com/">
-
比如这是淘宝网对 dns-prefetch 的使用
-
每当站点引用跨域域上的资源时,都应在 元素中放置 dns-prefetch 提示,但是要记住一些注意事项:
- dns-prefetch 仅对跨域域上的 DNS 查找有效,因此请避免使用它来指向当前的站点或域。这是因为,到浏览器看到提示时,当前站点或背后的 IP 已经被解析;
- dns-prefetch 需慎用,多页面重复 DNS 预解析会增加重复 DNS 查询次数;
- 默认情况下浏览器会对页面中和当前域名(正在浏览网页的域名!不在同一个域的域名进行预获取,井且缓存结果,这就是隐式的 DNS Prefetch,如果想对页面中没有出现的域进行预获取,那么就要使用显示 DNS Prefetch了;
- 虽然使用 DNS Prefetch 能够加快页面的解析速度.但是也不能滥用,因为有开发者指出禁用 DNS 预读取能节省每月10D亿的 DNS 查询:
<meta http-equiv="x-dns-prefetch-control" content="off">
更多 DNS 解析优化
-
延长 DNS 绶存时间;
-
尽可能使用 A 或 AAAA 记录代替 CNAME;
-
使用 CDN 加速域名;
-
自己搭建 DNS 服务;
附:清除 DNS 缓存
-
清除浏览器 DNS 缓存:
- 清除 DNS 缓存:chrome://net-interna Ls/#dns;
- 有时候也需要同时清除套接字级存池:chrome://net-internals/#sockets;
-
清除系统 DNS 缓存:
# 在 Windows 中查看 DNS 缓存记录 ipconfig /displaydns # 在 Windows 中清除 DNS 缓存记录 ipconfig /flushdns # 在 macOS 中清除 DNS 缓存记录 sudo killall -HUP mDNSResponder
HTTP 长连接
短连接
-
HTTP 协议的初始版本中,每进行一次 HTTP 通信就要断开一次 TCP 连接;
-
以早期的通信情况来说,因为都是些容量很小的文本传输,所以即使这样也没有多大问题,但是随着 HTTP 的大量普及,文档中包含大量富文本(图片、视频等资源)的情况多了起来;
-
比如,使用浏览器浏览一个包含多张图片的 HTML 页面时,在发送请求访问 HTML 页面资源的同对,也会请求该HTML 页面包含的其它资源,因此,每次的请求都会這成无谓的 TCP 连接建立和断开,增加通信的开销;
-
为了解决这个问题,有些浏览器在请求时,用了一个非标准的 Connection 字段:
Connection: keep-alive
,这个字段要求服务器不要关闭TCP 连接,以便其他请求复用,服务器同样回应这个字段;一个可以复用的 TCP 连接就建立了,直到客户媏或服务器主动关闭连接,但是,这不是标准字段,不同实现的行为可能不一致,因此不是根本的解决办法;
长连接
-
1997 年1月,HTTP/1.1版本发布,只比1.0版本晚了半年。它进一步完善了 HTTP 协议,直到现在还是最流行的版本;
-
HTTP 1.1 版的最大变化,就是引入了持久连接 (HTTP Persistent Connections),即 TCP 连接默认不关闭,可以被多个请求复用,不用声明 Connection: keep-alive;
-
持久连接的好处在于减少了 TCP 连接的重复建立和断开所造成的额外开销,减轻了服务器端的负载,另外,减少开销的那部分时间,使 HTTP 请求和响应能够更早的结束,这样 Web 页面的显示速度也就相应提高了;
-
客户端和服务器发现对方一段时间没有活动,就可以主动关闭连接,不过,规范的做法是,客户端在最后一个请求时,发送 Connection:close,明确要求服务器关闭 TCP 连接;
-
目前,对于同一个域名,大多数浏览器允许同时建立 6 个持久连接;
管道机制
-
HTTP 1.1 版还引入了管道机制 (pipelining),即在同一个 TCP 连接里面,客户端可以同时发送多个请求,这样就进一步改进了 HTTP 协议的效率;
-
从前发送请求后需等待并接收向应,才能发送下一个请求,管线化技术出现后,不用等待响应即可直接发送下一个请求:
- 这样就能够做到同时并行发送多个请求,而不需要一个接一个的等待响应了,与挨个连接相比,用持久连接可以让请求更快结束;
- 而管线化技术则比持久连接还要快,请求数越多,时间差就越明显;
-
举例来说,客户端需要请求两个资源,以前的做法是,在同一个 TCP 连接里面,先发送A请求,然后等待服务器做出回应,收到后再发出 B 请求;管道机制则是允许浏览器同时发出A请求和B请求,但是服务器还是按照顺序,先回应A请求,完成后再回应B:
Content-Length 字段
-
一个 TCP 连接现在可以传送多个回应,势必就要有一种机制,区分数据包是属于哪一个回应的,这就是 Content-length 宇段的作用,声明本次回应的数据长度
Content-Length: 3495
; -
上面代码告诉浏览器,本次回应的长厦是 3495 个字节,后面的字节就属于下一个回应了;
-
在1.0版中,Content-Length 字段不是必需的,因为浏览器发现服务器关闭了 TCP 连接,就表明收到的数据包已经全了;
分块传输编码
-
使用 Content-Length 字段的前提条件是,服务器发送回应之前,必须知道回应的数据长度;
-
对于一些很耗时的动态操作来说,这意味着,服务器要等到所有操作完成,才能发送数据,显然这样的效率不高,更好的处理方法是,产生一块数据,就发送一块,采用“流模式”(stream)取代“绶存模式”(buffer)
-
因此,1.1 版规定可以不使用 Content-Length 字段,而使用"分块传输编码"(chunked transfer encoding).只要请求或回应的头信息有 Transfer-Encoding 字段,就表明回应将由数虽未定的数据块组成;
-
每个非空的数据块之前,会有一个16进制的数值,表示这个块的长度,最后是一个大小为0的块,就表示本次回应的数据发送完了,下面是一个例子:
HTTP/1.1 200 OK Content-Type: text/plain Transfer-Encoding: chunked 25 This is the data in the first chunk 1C and this is the second one 3 con 8 sequence 0
长连接的缺点
-
虽然 HTTP 1.1版允许复用 TCP 连接,但是同一个 TCP 连接里面,所有的数据通信是按次序进行的:
- 服务器只有处理完一个回应,才会进行下一个回应;
- 要是前面的回应特别慢,后面就会有许多请求排队等着;这称为"队头堵塞"(Head-of-line blocking)
-
为了避免这个问题,只有两种方法:
- 一是减少请求数
- 二是同时多开持久连接
-
这导致了很多的网页优化汥巧,比如:
- 合并脚本和样式表;
- 将图片嵌入 CSS 代码;
- 域名分片 (domain sharding) 等等;
-
如果 HTTP 办议设计得更好一些,这些额外的工作是可以避免的;
HTTP 2
前言
-
2009年,谷歌公开了自行研发的 SPDY 协议,主要解决 HTTP/1.1效率不高的问题;
-
这个协议在 Chrome 浏览器上证明可行以后,就被当作 HTTP/2 的基础,主要特性都在 HTTP/2之中得到继承;
-
2015年,HTTP/2 发布,它不叫 HTTP/2.0,是因为标准委员会不打算再发布子版本了,下一个新版本将是HTTP/3;
二进制协议
-
HTTP/1.1 版的头信息是文本(ASCII 编码),数据体可以是文本,也可以是二进制;
-
HTTP/2 则是一个彻底的二进制协议,头信息和数据体都是二进制,井且统称为"帧"(trame):
- 头信息帧;
- 数据帧;
-
二进制系协议的一个好处是,可以定义额外的帧:
- HTTP/2 定义了近十种帧.为将来的高级应用打好了基础;
- 如果使用文本实现这种功能,解析数据将会变得非常麻烦,二进制解析则方便得多;
多工
-
HTTP/2 复用TCP 连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,而且不用按照顺序一一对应,这样就遊免了"队头堵塞;
-
举例来说,在一个 TCP 连接里面,服务器同时收到了A 请求和 B 请求,于是先回应A 请求,结果发现处理过程非常耗时,于是就发送A请求已经处理好的部分,接着回应 B 请求,完成后,再发送 A 请求剩下的部分;这样双工的,实时的通信,就叫做多工 (Multiplexing);
数据流
-
因为 HTTP/2 的数据包是不按顺序发送的,同一个连接里面连续的数据包,可能属于不同的回应,因此,必预要对数据包做标记,指出它展于哪个回应;
-
HTTP/2 将每个请求或回应的所有数据包,称为一个数据流(stream),每个数据流都有一个独一无二的编号;
- 数据包发送的时候,都必须标记数据流 ID,用来区分它属于哪个数据流;
- 另外还规定,客户端发出的数据流,ID一律为奇数,服务器发出的 ID 为偶数;
-
数据流发送到一半的时候,客户端和服务器都可以发送信号(RST_STREAM帧),取消这个数据流:
- 1.1版取消数据流的唯一方法,就是关闭 TCP 连接;
- 这就是说,HTTP/2 可以取消某一次请求,同时保证 TCP 连接还打开着,可以被其他请求使用;
-
客户端还可以指定数据流的优先级;优先级越高,服务器就会越早回应;
头信息压缩
-
HTTP 协议不带有状态,每次请求都必须附上所有信息,所以,请求的很多字段都是重复的,比如 Cookie 和 User Agent.的内容,每次请求都必须附带,这会浪费很多带宽,也影响速度;
-
HTTP/2 对这一点做了优化,引入了头信息压缩机制 (header compression):
-. 一方面,头信息使用 gzip 或 compress 压缩后再发送;- 另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了;
服务器推送
-
HTTP/2 允许服务器未经请求,主动向客户端发送资源,这叫做服务器推送 (server push);
-
常见场景是客户端请求一个网页,这个网页里面包含很多静态资源:
- 通常情况下,客户端必须收到网页后,解析 HTML 源码,发现有静态资源,再发出静态资源请求;
- 其实,服务器可以预测到客户端请求网页后,很可能会再请求静态资源,所以就主动把这些静态资源同网页一起发给客户端了;
避免重定向
概念
-
URL 重定向,也称为 URL 转发,是一种当实际资源:如单个页面、表单或者整个 Web 应用被迁移到新的 URL 下的时候,保持(原有)链接可用的技术,HTTP 协议提供了一种特殊形式的响应——HTTP 重定向(HTTP redireots) 来执行此类操作;
-
重定向可实现许多目标:
- 站点维护或停机期间的临时重定向;
- 永久重定向将在更改站点的 URL、上传文件时的进度页等之后保留现有的链接/书签;
- 上传文件时的表示进度的页面;
原理
-
在 HTTP 协议中,重定向操作由服务器通过发送特殊的响应(即 redirects) 而触发,HTTP 协议的重定向响应的状态码为3xx;
-
浏览器在接收到重定向响应的时候,会采用该响应提供的新的 URL,并立即进行加载;大多数情况下,除了会有一小部分性能损失之外,重定向操作对于用户来说是不可见的;
-
不同类型的重定向映射可以划分为三个类别:
- 永久重定向
- 临时重定向
- 特殊重定向
永久重定向
这种重定向操作是永久性,它表示原 URL 不应再被应用,而应该优先选用新的 URL,搜索引擎机器人会在遇到该状态码时触发更新操作,在其索引库中修改与该资源相关的 URL;
編码 | 含义 | 处理方法 | 典型应用场景 |
---|---|---|---|
301 | Moved Permanently | GET方法不会发生变更,其他方法有可能会变更为 GET方法 | 网站重构 |
308 | Permanent Redirect | 方法和消息主体都不发生变化 | 网站重构,用于非GET方法(with non-GET links/operations) |
临时重定向
有时候请求的资源无法从其标准地址访问,但是却可以从另外的地方访问,在这种情况下可以使用临时重定向;搜索引擎不会记录该新的、临时的链接,在创建、更新或者删除资源的时候,临时重定向也可以用于显示临时性的进度页面;
編码 | 含义 | 处理方法 | 典型应用场景 |
---|---|---|---|
302 | Found | GET方法不会发生变更,其他方法有可能会变更为 GET方法 | 由于不可预见的原因该页面暂不可用,在这种情况下,搜索引擎不会更新它们的链接; |
303 | See Other | GET方法不会发生变更,其他方法有可能会变更为 GET方法(消息主体会丢失) | 用于 PUT 或 POST 请求完成之后进行页面跳转来防止由于页面刷新寻致的操作的重复触发; |
307 | Temporary Redirect | 方法和消息主体都不发生改变 | 由于不可预见的原因该页面暂不可用,在这种情况下,搜索引擎不会更新它们的链接,当站点支持非GET 方法的链接或操作的时候,该状态码优于 302 状态码; |
特殊重定向
除了上述了两种常见的重定向之外,还有两种特殊的重定向:
- 304 (Not Modified,资源未被修改)会使页面跳转到本地陈旧的缓存版本当中(该缓存己过期(?)),
- 300 (Multiple Choice,多项选择)则是一种手工重定向:以 Web 页面形式呈现在浏览器中的消息主体包含了一个可能的重定向链接的列表,用户可以从中进行选择;
編码 | 含义 | 典型应用场景 |
---|---|---|
300 | Multiple Choice | 不常用:所有的选项在消息主体的 HTML 页面中列出,在 Link 头部加入机器可读的 rel=alternate |
304 | Not Modified | 发送用于重新验证的条件请求,表示缓存的响应仍然是新鲜的并且可以使用; |
示例代码
const http = require('http')
http.createServer((req, res) => {
res.writeHead(301, { Location: 'http://www.baidu.com' })
res.end()
}).listen(3000, () => {
console.log('running...')
})
参考链接
压缩传输的数据资源
数据压缩是提高 Web 站点性能的一种重要手段:
- 对于有些文件来说,高达 70% 的压缩比率可以大大减低对于带宽的需求;
- 隨着时间的推移,压缩算法的效率也越来越高,同时也有新的压缩算法被发明出来,应用在客户端与服务器端;
HTTP 响应数据压缩
-
压缩 JS、CSS:这里所说的压缩指的是去除换行空格之类的压缩,文件内容不变;
-
使用 Gzip 压缩文本
- 浏览器和服务器之间会使用主动协商机制:浏览器发送 Accept-Encoding 首部,其中包含有它所支持的压缩算法,以及各自的优先级,服务器则从中选择一种,使用该算法对响应的消息主体进行压缩,并且发送 Content-Encoding 首部来告知浏览器它选择了哪一种算法;由于该内容协商过程是基于编码类型来选择资源的展现形式的,在响应中: Vary 首部中至少要包含 Accept-Encoding:这样的话,缓存服务器就可以对资源的不同展现形式进行缓存;
- 下面是一个请求、响应的 HTTP 报文示例:
BASHBASH
GET /encrypted-area HTTP/1.1 Host: www.example.com Accept-Encoding: gzip, deflate
HTTP/1.1 200 OK Date: Tue, 27 Feb 2018 06:03:16 GMT Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux) Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT Accept-Ranges: bytes Content-Length: 438 Connection: close Content-Type: text/html; charset=UTF-8 Content-Encoding: gzip
-
压缩图片:在优化图片章节详细说明;
HTTP 请求数据压缩
头部数据压缩
-
HTTP 协议不带有状态,每次请求都必须附上所有信息,所以,请求的很多字段都是重复的,比如 Cookie 和 User Agent,一模一样的内容,每次请求都必预附带,这会浪费很多带宽,也影响速度;
-
HTTP/2对这一点做了优化,引入了头信息压缩机制 (header compression):
- 一方面,头信息使用 gzip 或 compress 压缩后再发送;
- 另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了;
请求体数据压缩
-
前面介绍了 HTTP 协议中的 Accept-Encoding/Content-Encoding 机制:
- 这套机制可以很好地用于文本类响应正文的压缩,可以大幅减少网络传输,从而一直被广泛使用;
- 但 HTTP 请求的发起方(例如浏览器),无法事先知晓要访问的服务媏是否支持解压,所以现阶段的浏览器没有压缩请求正文;
-
有一些通讯协议基于 HTTP 做了扩展,他们的客户端和服务端是专用的,可以放心大胆地压缩请求正文,例如 WebDAV 客户媏就是这样;
-
实际的 web 项目中,会存在请求正文非常大的场景,例如发表长篇博客,上报用于调试的网络数据等等,这些数据如果能在本地压缩后再提交,就可以节省网络流量、减少传输时间,本文介绍如何对 HTTP 请求正文进行压缩,包含如何在服务端解压、如何在客户端压缩两个部分;
-
开始之前,先来介绍本文涉及的三种数据压缩格式:
- DEFLATE:是一种使用 Lempel-Ziv 压缩算法(LZ77)和哈夫曼编码的压缩格式;
- ZLIB:是一种使用 DEFLATE 的压缩格式,对应 HTTP 中的 Content-Encoding: deflate;
- GZIP:也是一种使用 DEFLATE 的压缩格式,对应 HTTP 中的 Content-Encoding:gzip;
-
Content-Encoding 中的 deflate,实际上是 ZLIE,为了清晰,本文将 DEFLATE 称之为 RAW DEFLATE, ZLIB 和 GZIP 都是 RAW DEFLATE 的不同 Wrapper;
-
简单示例:
- 压缩请求正文:
var rawBody = 'content=test'; var rawLen = rawBody.length; var bufBody = new Uint8Array(rawLen); for (var i = 0; i < rawLen; i++) { bufBody[i] = rawBody.charCodeAt(i); } var format = 'gzip'; // gzip | deflate | deflate-raw var buf; switch (format) { case 'gzip': buf = window.pako.gzip(bufBody); break; case 'deflate': buf = window.pako.deflate(bufBody); break; case 'deflate-raw': buf = window.pako.deflateRaw(bufBody); break; } var xhr = new XMLHttpRequest(); xhr.open('POST', '/node/'); xhr.setRequestHeader('Content-Encoding', format); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); xhr.send(buf);
- 在 node 中解压请求正文的数据:
var http = require('http'); var zlib = require('zlib'); http.createServer(function(req, res) { var zlibStream; var encoding = req.headers['content-encoding']; switch (encoding) { case 'gzip': zlibStream = zlib.createGunzip(); break; case 'deflate': zlibStream = zlib.createInflate(); break; case 'deflate-raw': zlibStream = zlib.createInflateRaw(); break; } res.writeHead(200, { 'Content-Type': 'text/plain' }); req.pipe(zlibStream).pipe(res); }) .listen(8361, '127.0.0.1');
- 压缩请求正文:
-
实际使用还需要匹配具体的服务器,比如 nginx、Apache 等;
参考链接
HTTP 缓存
强制缓存
-
对于强制缓存而言,如果浏览器判断所请求的目标资源有效命中,则可直接从强制缓存中返回请求响应,无须与服务器进行任何通信:
-
在介绍强制缓存命中判断之前,首先来看一段响应头的部分信息:
-
其中与强制缓存相关的两个字段是 expires 和 cache-control,expires是在 HTTP 1.0 协议中声明的用來控制缓存失效日期时间戳的字段,它由服务器端指定后通过响应头告知浏览器,浏览器在接收到带有该字段的响应体后进行缓存;若之后浏览器再次发起相同的资源请求,便会对比 expires 与本地当前的时间戳,如果当前请求的本地时间戳小于 expires 的值,则说明浏览器缓存的响应还末过期,可以直接使用而无须向服务器端再次发起请求;只有当本地时间戳大于 expires 值发生缓存过期时,才允许重新向服务器发起请求;
-
从上述强制缓存是否过期的判断机制中不难看出,这个方式存在一个很大的漏洞,即对本地时间戳过分依赖,如果客户端本地的时间与服务器端的时间不同步,或者对客户端时间进行主动修改,那么对于缓存过期的判断可能就无法和预期相符;
-
为了解决 expires 判断的局限性,从 HTTP 1.1 协议开始新增了 cache-control 字段来对 expires 的功能进行扩展和完善:从上述代码中可见 cache-control 设置了 maxage=31536000 的属性值来控制响应资源的有效期,它是一个以秒为单位的时间长度,表示该资源在被请求到后的 31536000 秒内有效,如此便可避免服务器端和客户端时间戳不同步而造成的问题;除此之外,cache-control 还可配置一些其他属性值来更准确地控制缓存,下面来具体介绍;
-
-
no-cache 和 no-store:
- no-cache VS no-store
- 设置 no-cache:其表示为强制进行协商缓存(后面会说),即对于每次发起的请求都不会再去判断强制缓存是否过期;而是直接与服务器协商来验证缓存的有效性,若缓存末过期,则会使用本地缓存;
- 设置 no-store:则表示禁止使用任何缓存策略,客户端的每次请求都需要服务器端给予全新的响应;
- no-cache 和 no-store 是两个互斥的属性值,不能同时设置;
- 发送如下响应头可关闭缓存:
- 指定 no-cache 或 max-age=0 表示客户端可以缓存资源,每次使用缓存资源前都必须重新验证其有效性,这意味着每次都会发起 HTTP 请求,但当缓存内容仍有效时可以跳过 HTTP 响应应体的下载;
- no-cache VS no-store
-
private 和 public:
- private 和 public 也是 cache-control 的一组互斥属性值,它们用以明确响应资源是否可被代理服务器进行缓存;
- 若资源响应头中的 cache-control 宇段设置了 public 属性值,则表示响应资源既可以被浏览器緩存,又可以被代理服务器缓存;
- private 则限制了响应资源只能被浏览器级存,若未显式指定则默认值为 private;
- 对于应用程序中不会改变的文件,通常可以在发送响应头前添加缓存,这包括例如由应用程序提供的静态文件,例如:图像、CSS 文件、JavaScript 文件;
- private 和 public 也是 cache-control 的一组互斥属性值,它们用以明确响应资源是否可被代理服务器进行缓存;
-
max-age 和 s-maxage:
- max-age 属性值会比 s-maxage 更常用,它表示服务器端告知客户端浏览器响应资源的过期时长。在一般项目的使用场景中基本够用,对于大型架构的项目通常会涉及使用各种代理服务器的情况,这就需要考虑缓存在代理服务器上的有效性问题;这便是 s-maxage存在的意义,它表示缓存在代理服务器中的过期时长,仅当设置了 public 属性值时才有效;
- 由此可见 cache-control 能作为 expires 的完全替代方案,并且拥有其所不具备的一些缓存控制特性,在项目实践中使用它就足够了,目前 expires 还存在的唯一理由是考虑可用性方面的向下兼容;
协商缓存
-
顾名思义,协商缓存就是在便用本地缓存之前,需要向服务器端发起一次 GET 请求,与之协商当前浏览器保存的本地缓存是否已经过期;
- 通常是采用所清求资源最近一次的修改时间戳来判断的,为了便于理解,下面来看一个例子:假设客户端浏览器需要向服务器请求一个 nanifest.js 的 Javascript 文件资源,为了让该资源被再次请求时能通过协商缓存的机制使用本地缓存,那么首次返回该资源的响应头中应包含一个名为 Last-madified 的宇段,该字段的属性值为该 JavaScript 文件最近一次修改的时间戳,简略截取请求头与响应头的关键信息如下:
- 当我们刷新网页时,由于该 JavaScript 文件使用的是协商缓存,客户端浏览器无法确定本地缓存是否过期,所以需要向服务器发送一次 GET 请求,进行缓存有效性的协商,此次 GET 请求的请求头中需要包含一个 ifmodified-since 字段,其值正是上次响应头中 last-modified 的字段值;
- 当服务器收到该请求后便会对比请求资源当前的修改时间戳与 if-modified-since 字段的,,如果二者相同则说明缓存未过期,可继续使用本地绶存,否则服务器重新返回全新的文件资源,简略截取请求头与响应头的关键信息如下:
- 注意:
- 协商绶存判断级存有效的响应状态码是 304,即缓存有效响应重定向到本地级存上;
- 强制缓存若有效,则再次请求的响应状态码是 200;
- 通常是采用所清求资源最近一次的修改时间戳来判断的,为了便于理解,下面来看一个例子:假设客户端浏览器需要向服务器请求一个 nanifest.js 的 Javascript 文件资源,为了让该资源被再次请求时能通过协商缓存的机制使用本地缓存,那么首次返回该资源的响应头中应包含一个名为 Last-madified 的宇段,该字段的属性值为该 JavaScript 文件最近一次修改的时间戳,简略截取请求头与响应头的关键信息如下:
-
last-modifed 的不足
- 通过 last-modified 所实现的协商緩存能够满足大部分的使用场景,但也存在两个比较明显的缺陷:
- 首先它只是根据资源最后的修改时间戳进行判断的,虽然请求的文件资源进行了编辑,但内容并没有发生任何交化,时间戳也会更新,从而导致协商缓存时关于有效性的判断验证为失效,需要重新进行完整的资源请求,这无疑会造成网络带宽资源的浪费,以及延长用户获取到目标资源的时间;
- 其次标识文件资源修改的时间戳单位是秒,如果文件修改的速度非常快,假设在几百毫秒内完成,那么上述通过时间戳的方式来验证缓存的有效性,是无法识别出该次文件资源的更新的;
- 其实造成上述两种缺陷的原因相同,就是服务器无法仅依据资源修改的时间戳来识别出真正的更新,进而导致重新发起了请求,该重新请求却使用了缓存的 Bug 场景;
- 通过 last-modified 所实现的协商緩存能够满足大部分的使用场景,但也存在两个比较明显的缺陷:
-
基于 ETag 的协商缓存
为了弥补通过时间戳判断的不足:从 HTTP 1.1 规范开始新增了一个 ETag 的头信息,即实体标签 (Entity Tag)
其内容主要是服务器为不同资源进行哈希运算所生成的一个字符串,该字符串类似于文件指纹,只要文件內容编码存在差异,对应的ETag 标签值就会不同,因此可以使用 ETag 对文件资源进行更精准的变化感知;
-
下面来看一个使用 ETag 进行协商缓存图片资源的示例:
- 首次请求后的部分响应头关键信息如下:
- 上述响应头中同时包含了 Last-modified 文件修改时间戳和 ETag 实体标签,两种协商缓存的有效性校验字段,因为 ETag 比 last-madified 具有更准确的文件资源变化感知,所以它的优先级也更高,二者同时存在时以 ETag 为准;再次对该图片资源发起请求时,会将之前响应头中 ETag 的字段值作为此次请求头中 If-None-Match 字段,提供给服务器进行缓存有效性验证;请求头与响应头的关键字段信息如下:
- 再次请求头:
- 再次响应头:
- 再次请求头:
- 若验证缓存有效,则返回 304 状态码响应重定向到本地缓存,所以上面响应头中的内容长度 Content-Length 字段值也就为 0 了;
-
ETag 的不足
不像强制缓存中 cache-control 可以完全替代 expires 的功能,在协商缓存中,ETag 并非 last-modified 的替代方案而是一种补充方案,因为它依旧存在一些弊端:
- 一方面:服务器对于生成文件资源的 ETag 需要付出额外的计算开销,如果资源的尺寸较大,数量较多且修改比较频繁,那么生成 ETag的过程就会影的服务器的性能;
- 令一方面:ETag 字段值的生成分为强验证和弱验证,强验证根据资源内容进行生成,能够保证每个字节都相同;弱验证则根据资源的部分属性值来生成,生成速度快但无法确保每个字节都相同,井且在服务器集群场景下,也会因为不够准确而降低协商缓存有效性验证的成功率,所以恰当的方式是根据具体的资源使用场景选择恰当的缓存校验方式;
缓存决策
-
缓存决策树
- 在面对一个具体的缓存需求时,到底该如何制定缓存策略呢?可以参照如图所示的决策树来逐步确定对一个资源具体的缓存策略:
- 首先根据资源内容的属性判断是否需要使用缓存,如果不希望对该资源开启缓存(比如涉及用户的一些敏感信息),则可直接设置 cache-control 的属性值为 no-store 来禁止任何缓存策略,这样请求和响应的信息就都不会被存储在对方及中间代理的磁盘系统上;
- 如果希望使用缓存,那么接下来就需要确定对缓存有效性的判断是否要与服务器进行协商,若需要与服务器协商则可为 cache-control 字段增加 no-cache 属性值,来强制启用协商缓存;
- 否则接下来考虑是否允许中间代理服务器缓存该资源,参考之前在强制缓存中介绍的内容,可通过为 cache-control 字段添加 private 或 public 来进行控制;如果之前未设置 no-cache 启用协商缓存,那么接下来可设置强制缓存的过期时间,即为 cache-contral 字段配置 max-age=…的属性值,最后如果启用了协商缓存,则可进一步设置请求资源的 last-modified 和 ETag 实体标签等参数;这里建议能够根据该决策树的流程去设置缓存策略,这样不但会让指定的策略有很高的可行性,而且对于理解缓存过程中的各个知识点也非常有帮助;
- 在面对一个具体的缓存需求时,到底该如何制定缓存策略呢?可以参照如图所示的决策树来逐步确定对一个资源具体的缓存策略:
-
缓存决策示例
- 在使用缓存技术优化性能体验的过程中,有一个问题是不可逾越的:我们既希望缓存能在客户端尽可能久的保存,又希望它能在资源发生修改时进行及对更新;这是两个互斥的优化诉求,使用强制缓存并定义足够长的过期时问就能让缓存在客户端长期驻留,但由于强制缓存的优先级高于协商缓存,所以很难进行及时更新;若使用协商级存,虽然能够保证及时更新,但频繁与服务器进行协商验证的响应速度肯定不及使用强制缓存快,那么如何兼顾二者的优势呢?
- 我们可以将一个网站所需要的资源按照不同类型去拆解,为不同类型的资源制定相应的缓存策略,以下面的HTML 文件资源为例:
- 该 HTML 文件中包含了一个 Javascript 文件 script.js、一个样式表文件 style.css 和一个图片文件 photo.jpg,若要展示出该 HTML 中的内容就需要加载出其包含的所有外链文件,据此我们可针对它们进行如下设置;
- 首先 HTML 在这里属于包含其他文件的主文件,为保证当其内容发生修改时能及时更新,应当将其设置为协商缓存、即为 cache-control 字段添加 no-cache 属性值;其次是图片文件,因为网站对图片的修改基本都是更换修改,同时考虑到图片文件的数量及大小可能对客户端缓存空间造成不小的开销,所以可采用强制缓存且过期时间不宜过长.故可设置 cache-control 字段值为 max-age=86400;
- 接下来需要考虑的是样式表文件 style.css,由于其同于文本文件,可能存在内容的不定期修改,又想使用强制缓存来提高重用效率,故可以考虑在样式表文件的命名中增加文件指纹或版本号(比如添加文件指纹后的样式表文件名变为了 style.51ad84f7.css),这样当发生文件修改后,不同的文件便会有不同的文件指纹,即需要请求的文件 URL 不同了,因此必然会发生对资源的重新请求;同时考虑到网络中浏览器与 CDN 等中间代理的缓存,其过期时间可适当延长到一年,即 cache-control: max-age=31536000;
- 最后是 JavaScript 脚本文件,其可类似于样式表文件的设置,采取文件指纹和较长的过期时间,如果 JavaScript 中包含了用户的私人信息而不想让中间代理缓存,则可为 cache-control 添加 private 属性值;
- 从这个缓存策略的示例中可以看出,对不同资源进行组合使用强制缓存、协商缓存及文件指纹或版本号,可以做到一举多得:及时修改更新、较长缓存过期时间及控制所能进行緩存的位置;
-
缓存设置注意事项
- 拆分源码,分包加载:对大型的前端应用送代开发来说,其代码量通常很大,如果发生修改的部分集中在几个重裝模快中,那么进行全量的代码更新显然会比较冗余,因此可以考虑在代码构建过程中,按照模块拆分将其打包成多个单独的文件,这样在每次修改后的更新新提取时,仅需拉取发生修改的模块代码包,从而大大降低了需要下载的内容大小;
- 预估资源的缀存时效:根据不同资源的不同需求特点,规划相应的缓存更新时效,为强制缓存指定合适的 max-age 取值,为协商缓存提供验证更新的 ETag 实体标签;
- 控制中间代理的缓存:凡是会涉及用户隐私信息的尽量避免中间代理的缓存,如果对所有用户响应相同的资源,则可以考虑让中间代理也进行缓存;
- 遯免网址的冗余:缓存是根据请求资源的 URL 进行的,不同的资源会有不同的 URL,所以尽量不要将相同的资源设置为不同的 URL;
- 规划缓存的层次结构:参考缓存决策中介绍的示例,不仅是请求的资源类型,文件资源的层次结构也会对制定缓存策略有一定影响,应当综合考虑;
注意事项
缓存是限定域名的
- 根域下的缓存是共享的:比如 a.com、 foo.a.com、 bar.a.com 的根或都是 a.com,他们是共享缓存;
- 同理,域名不同的缓存不共享:比如 a.com,b.com、c.com,也们之间即使加载相同资源也仅在该域名下有效,不共享;
代码附件下载
Service Worker 缓存
CDN 缓存
CDN 概述
-
CDN 全称 Content Deivery Network,即内容分发网络,它是构建在现有网络基础上的虚拟智能网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、调度及内容分发等功能模块,使用户在请求所需访问的内容时能够就近获取,以此来降低网络拥塞,提高资源对用户的响应速度;
-
前面讲到的浏览器缓存方案,它们带来的性能提升主要针对的是浏览器端已经缓存了所需的资源,当发生二次清求相同资源时便能够进行快速响应,遊免重新发起请求或重新下载全部响应资源;
-
显而易见,这些方法对于首次资源请求的性能提升是无能为力的,若想提升首次请求资源的响应速度,除了对资源进行压缩、图片优化等方式,还可借助本节所要介绍的 CDN 技术;
工作原理
-
回想在初学计算机网络的时候,常见的 B/S 模型都是浏览器直接向服务器请求所需的资源,但实际组网情况井非如此简单;因为通常对热门站点來说,同时发起资源请求的用户规模量往往非常巨大,而如果这些请求都发往同一服务器则极有可能造成访问拥塞;所以更合理的做法是将部分数据缓存在距离用户较近的边缘服务器上,这样不但可以提升对资源的请求荻取速度,而且也能有效减少网站根节点的出口带宽压力,这便是 CDN 技术的基本思路;
-
如果未使用 CDN 网络进行缓存加速,那么通过浏览器访问网站获取资源的大致过程如图所示:
- 当用户在浏览器中输入所要访问的域名时,若本机无法完成域名解析工作,则会转向 DNS 服务器请求对该域名的解析;
- DNS 服务器解析完成返回给浏览器该域名所对应的IP 地址;
- 浏览器向该 IP 地址指向的服务器发起资源请求;
- 最后服务器响应用户请求将资源返回给浏览器;
-
如果使用了 CDN 网络,则资源获取的大致过程是这样的:
- 由于 DNS 服务器将对 CDN 的域名解析权交给了 CNAME 指向的专用 DNS服务器,所以对用户输入域名的解析最终是在 CDN 专用的 CNS 服务器上完成的;
- 解析出的结果 IP 地址井非确定的 CDN 缓存服务器地址,而是 CDN的负载均街器的地址;
- 浏览器会重新向该负载均街器发起请求,经过对用户 IP 地址的距离、所请求资源内容的位置及各个服务器复杂状况的综合计算,返回给用户确定的缓存服务器IP 地址;
- 对目标缓存服务器请求所需资源的过程;
-
当然这个过程也可能会发生所需资源未找到的情况,那么此时便会依次向其上一级缓存服务器继续请求查询,直至追溯到网站的根服务器并将资源拉取到本地,如图所示;虽然这个过程看起来稍微复杂了一些,但对用广体验来说是无感知的,井且能带来比较明显的资源加载速度的提升,因此对目前所有一线互联网产品来说,使用 CDN 已经不是一条建议,而是一个规定;
针对静态资源
-
CDN 网络能够缓存网站资源来提升首次请求的响应速度,但并非能适用于网站所有资源类型,它往往仅被用来存放网站的静态资源文件;所谓静态资源,就是指不需要网站业务服务器參与计算即可得到的资源,包括第三方库的 JavaSeript 脚本文件、样式表文件及图片等;这些文件的特点是访问频率高、承载流量大,但更新修改频次低,且不与业务有太多耦合;
-
如果是动态资源文件,比如依赖服务器渲染得到的 HTML页 它需要借助服务器端的数据进行计算才能得到,所以它就不适台放在 CDN 缓存服务器上;
核心功能
-
CDN 网络的核心功能包括两点:缓存与回源;
- 緩存指的是将所需的静态资源文件复制一份到 CDN 缓存服务器上;
- 回源指的是如果未在 CDN 缓存服务器上查找到目标资源,或 CDN 缓存服务器上的缓存资源已经过期,则重新追溯到网站的根服务器获取相关资源的过程;
-
由于这两个过程与前端性能优化的关系并非特别紧密,所以此仅介绍概念,暂不进行深入分析;
应用场景
-
下面以淘宝官网为例,来看看关于 CDN 的具体使用情况,打开淘宝网址可查看页面最终的渲染效果,如图所示:
-
此时打开 Chrome 开发者工具的 Network 选项卡,来查看网站为渲染出该效果都请求了哪些资源,很容易发现除了从业务服务器返回的一个未完全加载资源的 HTML 文件,还包括了许多图片、JavaScript 文件及样式表文件,具体内容如图所示:
-
接着进一步去查看静态资源所请求的URL,并列举几种不同类型的资源文件如下:
-
从上述资源文件的请求域名中可以发现,这些文件都是从 CDN 网络上获取的,javascript 和样式表这样的文本文件与图片文件使用的是不同的 CDN 域名,而且 CDN 域名与主站域名也完全不同,这样的设计也是出于对性能的考感,下面分析具体的优化原理;
优化实践
-
关于 CDN的性能优化、如何能将其效果发挥到最大程度?其中包括了许多可实践的方面,比如 CDN 服务器本身的性能优化、动态资源静态边缘化、域名合并优化和多级缓存的架构优化等,这些可能需要前端工程师与后端工程师一起配台,根据具体场景进行思考和解决,这里仅介绍一个与前端关系密切的 CDN 优化点:域名设置;
-
以上节的示例来进行说明,主站请求的域名为 www.taobao.con ,而静态资源请求 CDN 服务器的域名有 g.alicdn.com 和 img.alicdn.ccm 两种,他们是有意设计成与主站域名不同的,这样做的原因主要有两点:第一点是避免对静态资源的请求携带不必要的 Cookie 信息;第二点是考虑浏览器对同一域名下并发请求的限制;
- 首先对第一点来说:Cookie 的访问遵循同源策略,并且同一域名下的所有请求都会携带全部 Cookie 信息,虽然 Cookie 的存储空间就算存满也并不是很大,但如果将所有资源请求都放在主站域名下,那么所产生的效果对于任何一个图片、Javescript 脚本及样式表等静态资源文件的请求,都会携带完整的 Cookie 信息,若这些完全没有必要的开销积少成多,那么它们所产生的流量浪费就会很大:所以将 CDN 服务器的域名和主站域名进行区分是非常有价值的实践;
- 其次是第二点:因为浏览器对于同域名下的并发请求存在限制,通常 Chrome 的并发限制数是6,其他浏览器可能多少会有所差异,这种限制也同时为我们提供了一种解决方案:通过增加类似域名的方式来提高并发请求数,比如对多个图片文件进行并发请求的场景,可以通过扩展如下类似域名的方式来规避限制:
- 虽然这种方式对于多并发限制是有效的,但是缓存命中是要根据整个 URL 进行匹配的,如果并发请求了相同的资源却又使用了不同的域名,那么图片之前的缓存就无法重用,也降低了缓存的命中,对于这种情况应该考虑进行怡当的域名合并优化;
Push 缓存
概念
-
HTTP 2 新增了一个强大的功能:服务器端推送,它的出现打破了传统意义上的请求与响应一对一的模式,服务器可以对客户端浏览器的一个请求发送多个响应;
-
这样会带来性能优化的一个新思路:
- 在传统的网络应用中,客户端若想将应用中所包含的多种资源渲染展示在浏览器中,就需要逐个资源进行请求,但其实一个 HTML 文件中所包含的 JavaScript、样式表及图片等文件资源,是服务器可以在收到该 HTML 请求后预判出稍后会到来的请求,那么就可以利用服务器端推送节省这些多余的资源请求,来提升页面加载的速度;
- 显然 Push 缓存能显著提升页面加载速度,但在具体使用过程中依然有许多需要注意的地方;
最后一道缓存
-
内存中的缓存
- 内存中的缓存是浏览器中响应速度最快且命中优先级最高的一种缓存,但它的驻留周期非常短,通常依赖于渲染进程,一旦页面页签关闭进程结束,内存中的缓存数据就会被回收;
- 具体到什么资源会放入内存中的缓存,其实具有一定的随机性,因为内存空间有限,首先需要考虑到当前的内存余量,然后再视具体的情况去分配内存与磁盘空间上的存储占比;通常体积不大的 javaScript 文件和样式表文件有一定概率会被纳入内存中进行缓存,而对于体积较大的文件或图片则较大概率会被直接放在磁盘上存储;
-
缓存命中优先级
- 上述四类浏览器缓存的命中优先级从高到低分别是:内存中的级存、Service Worker 缓存、HTTP 缓存、HTTP 2 的 Push 缓存;
- Push 缓存会作为缓存命中的最后一道防线,只有在前面三种缓存均末命中的情况下才会讲行询问,这里需要注意的是,只要有高优先级的缓存命中成功,即便设置了低优先级的缓存,也不会对其进行询问;
- 缓存命中优先级如图所示:
-
基于连接的缓存
- 在了解了缓存命中优先级后,还需要明白 Push 缓存是依赖于 HTTP2 连接的,如果连接断开,即便推送的资源具有较高的可缓存性,它们也会丢失,这就意味着需要建立新的连接并重新下资源;考虑到网络可能存在不稳定性,建议不要长时间依赖 Push 缓存中的资源内容,它更擅长的是资源推送到页面提取,间隔时长较短的使用场景;
- 另外,每个 HTTP2 连接都有自己独立的 Push 缓存,对使用了同一个连接的多个页面来说,它们可以共享该 Push 缓存;但反过来看也需要明白,在将如 JSON 数据等内容与页面响应信息一同推送给客户端时,这些数据资源并非仅被同一页面提取,它们还可以被一个正在安装的 Service Worker 提取使用,这或许会成为 Push 缓存的一个优势;
Push 缓存与预加载
相同点
-
通过讲述有关 HTTP2 推送的内容,可以察觉到它与 HTTP 的预加载存在许多相似之处;
-
它们的优化原理都是利用客户端的空闲带宽来进行资源文件获取的,这种方式能够很好地将资源的执行与获取进行分商,当浏览器实际需要某个资源文件时,该资源文件其实已经存在于缓存中了,这样便省去了发起请求后的等待时间;
不同之处
-
Push 缓存是由服务器端决定何时向客户端预先推送资源的,而预加载则是当客户端浏览器收到 HTML 文件后,经过解析其中带有 preload 的标签,才会开启预加载的;
-
Push 缓存只能向同源或具有推送权的源进行资源的推送,而预加载则可以从任何源加载资源;
-
预加载使用的是内存中的缓存,而推送使用的 Push 缓存;
-
预加载的资源仅能被发起请求的页面使用,而服务器端 Push 缓存的资源却能在浏览器的不同标签页面中共用;
-
预加载使用的 link 标签上可以设罝 onload 和 onerror 进行相应事件的监听,而 Push 缓存则在服务器端进行监听相对更加透明;
-
预加载可以根据不同的头信息,使用内容协商来确定发送的资源是否正确,Push 缓存却不可以;
使用场景
-
Push 缓存的两个场景:
- 有效利用服务器的空闲时间进行资源的预先推送:例如对于服务器端渲染HTML 页面的场景,在服务器端生成 HTML 页面的过程中,网络是处于空闲状态的,并且此时客户端浏览器也不会知道将要展示的页面中会包含哪些资源,那么便可以利用这段时间向浏览器推送相关资源;
- 推送 HTML 中的内联资源:比如 JavaScript脚本、样式表文件和一些小图标,将这些资源文件进行单独推送,同时也可以很好地利用浏览器缓存,避免每次将 HTML 文件及所包含的资源一并推送;
-
预加载的两个场景:
- CSS 样式表文件中所引用的字体文件;
- 外部 CSS 样式表文件中使用 background-url 属性加载的图片文件;
使用决策
-
为了方便决定使用 Push 缓存还是预加载,下面给出一个决策树以供参考,如图展示:
-
在上图的决策树中,如果资源并不能够利用服务器端空闲时间进行推送,可能就需要根据具体场景进行选取了:
- 如果是内联的关键样式表或 Javascript 脚本,仅希望更快进行加载则可以使用预加载;
- 对于某些在服务器端就能预判出稍后便会请求的资源,则可使用 Push 推送进行提前缓存;
使用服务端渲染
前端页面的生命周期
上一篇