浏览器加载网络资源的速度,其中一个重要手段是利用缓存
Web 缓存按存储位置来区分,包括数据库缓存、服务端缓存、CDN 缓存和浏览器缓存。下面主要介绍浏览器缓存。
浏览器缓存的核心问题是:如何保证缓存与实际资源一致的同时,提高缓存的命中率(尽可能地让浏览器从缓存中获取资源,但同时又要保证被使用的缓存与服务端最新的资源保持一致)。
http缓存的分类及原理
http缓存有两种缓存过期策略,即强缓存和协商缓存。
强缓存
在设定的过期时间之前,浏览器都不会再向服务器请求资源,而是直接使用浏览器缓存 。
expires
http 1.0时使用响应头 expires 来实现:浏览器第一次向服务器请求资源时,服务器响应时,会在响应头expires设置一个过期时间,在这个过期时间前,浏览器如果再次请求,会直接从浏览器缓存中查找,如果找不到,才会向服务器请求。
Expires: Thu,21 Jan 2017 23:39:02 GMT
Expires表示的是绝对时间,是服务器的绝对时间,可能和客户端时间不一致,也就是客户端可以通过手动修改系统时间,从而导致缓存延长使用或提前过期的问题
Cache-Control
http 1.1增加Cache-Control响应头来改善这个问题,它可以有以下几个值:
- no-cache:表示使用协商缓存,即每次使用缓存前必须向服务端确认缓存资源是否更新;
- no-store:禁止浏览器以及所有中间缓存存储响应内容;
- public:公有缓存,表示可以被代理服务器缓存,可以被多个用户共享;
- private:私有缓存,不能被代理服务器缓存,不可以被多个用户共享;
- max-age:以秒为单位的数值,表示缓存的有效时间;
- must-revalidate:当缓存过期时,需要去服务端校验缓存的有效性。
Cache-Control:max-age=3600
(这个是相对时间,是相对于客户端时间的3600s)
如果 expires 和 Cache-Control 两者同时存在,则以cache-control为准,强缓存是由服务器设置的,但是是由浏览器来判断缓存是否还有效
协商缓存
不再设置缓存时间,而是直接由浏览器向服务器发送请求进行缓存确认,如果服务器返回304状态码,则表示缓存仍然有效。
Last-Modified和If-Modified-Since
利用服务器和客户的修改时间比对来判断:
- 客户端第一次向服务器请求资源
- 服务器返回资源,并在响应头设置 Last-Modified告知上次修改时间
- 客户端再次请求资源时,会将服务器上次返回的Last-Modified修改时间,作为这次请求头If-Modified-Since的时间,用于询问服务器自这个时间起,这个资源是否修改了
- 服务器收到请求后,比对双方资源修改时间来判断缓存是否过期,如果没过期,返回304;如果过期了,重新返回资源和更新时间
- 响应头:
Last-Modified Last-Modified: Wed, 26 Jan 2017 00:35:11 GMT
- 请求头:
If-Modified-Since:Wed, 26 Jan 2017 00:35:11 GMT
但这种做法仍存在问题:
- 时间精度问题:如果修改文件是在1s以内,内容修改了,但时间没变,误判使用缓存
- 内容准度问题:我打开了文件,没修改任何内容(或者修改后又改回去了),重新保存,内容没变,但时间变了,会误判不使用缓存
ETag 和 If-None-Match
为了解决以上两个问题,http提供一种基于文件哈希值来判断缓存的方式:
- 客户端第一次请求资源
- 服务端返回资源,响应头添加 ETag,为资源文件的哈希值
- 客户端再次请求时,以上次服务器的ETag值作为自己这次请求头If-None-Match的值,用于询问服务器文件是否修改过
- 服务器比对If-None-Match的值和现在资源的哈希值,如果没变,返回304,使用缓存;否则返回新资源和对应的ETag值
- 响应头:
Etag
- 请求头:
If-None-Match
存在的问题:
- 计算成本高,当文件大时,计算它的哈希值开销比较大
- 当资源放置在不同服务器时,不同服务器计算哈希值方式不一样时,返回的ETag值不一样,导致相同的资源却没命中缓存。即使用服务器集群时,可能会降低缓存命中率。
协商缓存是由客户端和服务端共同设置的,由服务端判断是否命中缓存。
优先级:
- 强缓存高于协商缓存
- 强缓存中:cache-control高于expires
- 协商缓存中:Etag高于last-modified
Service Worker
ServiceWorker 是浏览器在后台独立于网页运行的脚本,也可以这样理解,它是浏览器和服务端之间的代理服务器。ServiceWorker 非常强大,可以实现包括推送通知和后台同步等功能,更多功能还在进一步扩展,但其最主要的功能是实现离线缓存。
主要实现原理:拦截浏览器请求并返回缓存的资源文件。
大概实现流程是这样的:
- 先使用register注册serviceWorker脚本,浏览器获取到脚本后会解析然后进行安装
- 通过监听install事件来监听安装,当安装完成后激活脚本
- 激活脚本后,可以监听fetch事件来拦截请求并加载缓存的资源
因为它的功能很强大,所以浏览器对其做了很多限制:
- 在 ServiceWorker 中无法直接访问 DOM,但可以通过 postMessage 接口发送的消息来与其控制的页面进行通信;
- ServiceWorker 只能在本地环境下或 HTTPS 网站中使用;
- ServiceWorker 有作用域的限制,一个 ServiceWorker 脚本只能作用于当前路径及其子路径;
- 兼容性不太好(IE不支持),具体可以到caniuse网站查询
网友评论