总括: 缓存历来都是前端的一个痛点,不少前端搞不清楚缓存究竟是何物,从而给本身创造了一些麻烦,本文一如既往的用通俗易懂的文字和实例来说述缓存,但愿能让您有所得。javascript
原文博客地址: 缓存详解css
知乎专栏&&简书专题:前端进击者(知乎)html
博主博客地址:Damonare的我的博客前端
天青色等烟雨,而我在等你。java
缓存是一种保存资源副本并在下次请求时直接使用该副本的技术。node
说实话,我起始真的不知道怎么去介绍缓存,因此引用了上面相对官方的定义。我想几乎每一个开发者都碰到过缓存的问题吧,甚至有不少状况下咱们会说这个问题已经修复了,你清理下缓存就行了
。这篇文章咱们就细细的来挖掘下缓存的种种轶事。jquery
不少开发者习惯把cookie、webStorage以及IndexedDB存储的数据也称之为缓存,理由是都是保存在客户端的数据,没有什么区别。其实这是不严谨的,cookie的存在更多的是为了让服务端区别用户,webStorage和IndexedDB则更多用在保存具体的数据和在客户端存储大量结构化数据(文件/blobs)上面。web
实际上所谓的缓存只有一种——它是请求资源的副本。试想一下,若是每个资源咱们客户端都会保存一份副本,这会怎么样?客户端会炸掉,开发者会疯掉!因此咱们须要一份协议来处理缓存,可让开发者控制缓存的创建和删除。谁呢?还能有谁,HTTP
呗。HTTP协议里定义了不少关于缓存的请求和响应字段,这也是接下来咱们重点要逼逼叨的对象,研究下到底是哪些字段怎么影响缓存的。算法
纳尼?你问我为何要缓存?😱chrome
那就太容易说道了🤣,缓存好处有不少:
🤦♀️那么问题又来了,既然缓存这么好,若是我请求的服务器中间有代理也缓存了怎么办?代理服务器缓存了个人资源致使我无法从源服务器拿到最新的资源怎么办?HTTP固然也想到了这块的诉求。接下来咱们也会逐层剖析。
🍉缓存在宏观上能够分红两类:私有缓存和共享缓存。共享缓存就是那些能被各级代理缓存的缓存(咋以为有点绕)。私有缓存就是用户专享的,各级代理不能缓存的缓存。
🐜微观上能够分下面三类:
我相信只要你常用某个浏览器🌎(Chrome,Firefox,IE等),确定知道这些浏览器在设置里面都是有个清除缓存功能,这个功能存在的做用就是删除存储在你本地磁盘上资源副本,也就是清除缓存。
缓存存在的意义就是当用户点击back按钮或是再次去访问某个页面的时候可以更快的响应。尤为是在多页应用的网站中,若是你在多个页面使用了一张相同的图片,那么缓存这张图片就变得特别的有用。😏
代理服务器缓存原理和浏览器端相似,但规模要大得多,由于是为成千上万的用户提供缓存机制,大公司和大型的ISP提供商一般会将它们设立在防火墙上或是做为一个独立的设备来运营。(下文若是没有特殊说明,全部提到的缓存服务器都是指代理服务器。)
因为缓存服务器不是客户端或是源服务器的一部分,它们存在于网络中,请求路由必须通过它们才会生效,因此实际上你能够去手动设置浏览器的代理,或是经过一个中间服务器来进行转发,这样用户天然就察觉不到代理服务器的存在了。🤥
代理服务器缓存就是一个共享缓存,不仅为一个用户服务,常常为大量用户使用,所以在减小相应时间和带宽使用方面颇有效:由于同一个缓存可能会被重用屡次。
也被称为代理缓存或反向代理缓存,网关也是一个中间服务器,网关缓存通常是网站管理员本身部署,从让网站拥有更好的性能。🙂
CDNS(网络内容分发商)分布网关缓存到整个(或部分)互联网上,并出售缓存服务给须要的网站,好比国内的七牛云、又拍云都有这种服务。
数据库缓存是指当咱们的应用极其复杂,表天然也很繁杂,咱们必须进行频繁的进行数据库查询,这样可能致使数据库不堪重负,一个好的办法就是将查询后的数据放到内存中,下一次查询直接从内存中取就行了。关于数据库缓存本篇不会展开。🙃
缓存的目标:
以上,对于咱们能够和应该缓存的目标有个了解。🤗
浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来肯定的。
那么浏览器怎么肯定一个资源该不应缓存,如何去缓存呢❓响应头!响应头!响应头!重要的事情说三遍。✌️
咱们看🌰:
Age:23146
Cache-Control:max-age=2592000
Date:Tue, 28 Nov 2017 12:26:41 GMT
ETag:W/"5a1cf09a-63c6"
Expires:Thu, 28 Dec 2017 05:27:45 GMT
Last-Modified:Tue, 28 Nov 2017 05:14:02 GMT
Vary:Accept-Encoding
复制代码
以上请求头来自百度首页某个CSS文件的响应头。我去除了一些和缓存无关的字段,只保留了以上部分。咱们来分析下,Expires
是HTTP/1.0中的定义缓存的字段,它规定了缓存过时的一个绝对时间。Cache-Control:max-age=2592000
是HTTP/1.1定义的关于缓存的字段,它规定了缓存过时的一个相对时间。优先级上固然是版本高的优先了,max-age > Expires
。
这就是强缓存阶段,当浏览器再次试图访问这个CSS文件,发现有这个文件的缓存,那么就判断根据上一次的响应判断是否过时,若是没过时,使用缓存。加载文件,OVER!✌️
Firefox浏览器表现为一个灰色的200状态码。
Chrome浏览器状态码表现为:
200 (from disk cache)或是200 OK (from memory cache)
**多说一点:**关于缓存是从磁盘中获取仍是从内存中获取,查找了不少资料,得出了一个较为可信的结论:Chrome会根据本地内存的使用率来决定缓存存放在哪,若是内存使用率很高,放在磁盘里面,内存的使用率很高会暂时放在内存里面。这就能够比较合理的解释了为何同一个资源有时是from memory cache有时是from disk cache的问题了。
那么当这个CSS文件过时了怎么办?ETag
和Last-Modified
就该闪亮登场了。
先说Last-Modified
,这个字段是文件最后一次修改的时间;
ETag
呢?ETag是对文件的一个标记,嗯,能够这么说,具体生成方式HTTP并无给出一个明确的方式,因此理论上只要不会重复生成方式无所谓,好比对资源内容使用抗碰撞散列函数,使用最近修改的时间戳的哈希值,甚至只是一个版本号。
####2. 协商缓存阶段
利用这两个字段浏览器能够进入协商缓存阶段,当浏览器再次试图访问这个CSS文件,发现缓存过时,因而会在本次请求的请求头里携带If-Moified-Since
和If-None-Match
这两个字段,服务器经过这两个字段来判断资源是否有修改,若是有修改则返回状态码200和新的内容,若是没有修改返回状态码304,浏览器收到200状态码,该咋处理就咋处理(至关于首次访问这个文件了),发现返回304,因而知道了本地缓存虽然过时但仍然能够用,因而加载本地缓存。而后根据新的返回的响应头来设置缓存。(这一步有所差别,发现不一样浏览器的处理是不一样的,chrome会为304设置缓存,firefox则不会)😑
具体两个字段携带的内容以下(分别和上面的Last-Modified
、ETag
携带的值对应):
If-Moified-Since: Tue, 28 Nov 2017 05:14:02 GMT
If-None-Match: W/"5a1cf09a-63c6"
复制代码
到这协商缓存结束。
咱们把上面的响应头改下:
Age:23146
Cache-Control: public
Date:Tue, 28 Nov 2017 12:26:41 GMT
Last-Modified:Tue, 28 Nov 2017 05:14:02 GMT
Vary:Accept-Encoding
复制代码
发现没?浏览器用来肯定缓存过时时间的字段一个都没有!那该怎么办?有人可能会说下次请求直接进入协商缓存阶段,携带If-Moified-Since
呗,不是的,浏览器还有个启发式缓存阶段😎
根据响应头中2个时间字段 Date 和 Last-Modified 之间的时间差值,取其值的10%做为缓存时间周期。
这就是启发式缓存阶段。这个阶段很容让人忽视,但实际上每时每刻都在发挥着做用。因此在从此的开发过程当中若是遇到那种默认缓存
的坑,不要叫嚣,不要生气,浏览器只是在遵循启发式缓存协议而已。
我画了下面这张图,来解释浏览器整个缓存策略的过程:
👌对于缓存策略介绍到这,接下来再细细分析不一样的HTTP首部字段的内容,以及它们之间的关系。
HTTP报文是什么呢?就是HTTP报文,这是一个概念,主要由如下两部分构成:
以上咱们知道浏览器对于缓存的处理过程,也简单的提到了几个相关的字段。🤧接下来咱们具体看下这几个字段:
字段名称 | 说明 |
---|---|
Cache-Control | 控制缓存具体的行为 |
Pragma | HTTP1.0时的遗留字段,当值为"no-cache"时强制验证缓存 |
Date | 建立报文的日期时间(启发式缓存阶段会用到这个字段) |
字段名称 | 说明 |
---|---|
ETag | 服务器生成资源的惟一标识 |
Vary | 代理服务器缓存的管理信息 |
Age | 资源在缓存代理中存贮的时长(取决于max-age和s-maxage的大小) |
字段名称 | 说明 |
---|---|
If-Match | 条件请求,携带上一次请求中资源的ETag,服务器根据这个字段判断文件是否有新的修改 |
If-None-Match | 和If-Match做用相反,服务器根据这个字段判断文件是否有新的修改 |
If-Modified-Since | 比较资源先后两次访问最后的修改时间是否一致 |
If-Unmodified-Since | 比较资源先后两次访问最后的修改时间是否一致 |
字段名称 | 说明 |
---|---|
Expires | 告知客户端资源缓存失效的绝对时间 |
Last-Modified | 资源最后一次修改的时间 |
HTTP/1.1一共规范了47种首部字段,而和缓存相关的就有以上12个之多。接下来的两个小节会一个一个介绍给你们。🤓
经过cache-control的指令能够控制告诉客户端或是服务器如何处理缓存。这也是11个字段中指令最多的一个,咱们先来看看请求指令:
指令 | 参数 | 说明 |
---|---|---|
no-cache | 无 | 强制源服务器再次验证 |
no-store | 无 | 不缓存请求或是响应的任何内容 |
max-age=[秒] | 缓存时长,单位是秒 | 缓存的时长,也是响应的最大的Age值 |
min-fresh=[秒] | 必需 | 指望在指定时间内响应仍然有效 |
no-transform | 无 | 代理不可更改媒体类型 |
only-if-cached | 无 | 从缓存获取 |
cache-extension | - | 新的指令标记(token) |
响应指令:
指令 | 参数 | 说明 |
---|---|---|
public | 无 | 任意一方都能缓存该资源(客户端、代理服务器等) |
private | 可省略 | 只能特定用户缓存该资源 |
no-cache | 可省略 | 缓存前必须先确认其有效性 |
no-store | 无 | 不缓存请求或响应的任何内容 |
no-transform | 无 | 代理不可更改媒体类型 |
must-revalidate | 无 | 可缓存但必须再向源服务器进确认 |
proxy-revalidate | 无 | 要求中间缓存服务器对缓存的响应有效性再进行确认 |
max-age=[秒] | 缓存时长,单位是秒 | 缓存的时长,也是响应的最大的Age值 |
s-maxage=[秒] | 必需 | 公共缓存服务器响应的最大Age值 |
cache-extension | - | 新指令标记(token |
请注意no-cache指令不少人误觉得是不缓存,这是不许确的,no-cache的意思是能够缓存,但每次用应该去想服务器验证缓存是否可用。no-store才是不缓存内容。另外部分指令也能够组合使用,好比:
Cache-Control: max-age=100, must-revalidate, public
复制代码
上面指令的意思是缓存的有效时间为100秒,以后访问须要向源服务器发送请求验证,此缓存可被代理服务器和客户端缓存。
这是HTTP/1.0里面的一个字段,但优先级很高,测试发现,Chrome和Firefox中Pragma的优先级高于Cache-Control和Expires,为了向下兼容,这个字段依然发挥着它的做用。🤔通常可能咱们会这么用:
<meta http-equiv="Pragma" content="no-cache">
复制代码
Pragma属于通用首部字段,在客户端上使用时,常规要求咱们往html上加上上面这段meta元标签(并且可能还得作些hack放到body后面去。
事实上这种禁用缓存的形式用处颇有限:
Cache-Control: no-store
的meta标签(见出处)读者能够自行拷贝后面模拟服务端决策的代码进行测试。
服务端响应添加'Pragma': 'no-cache'
,浏览器表现行为和强制刷新相似。
这又是一个HTTP/1.0的字段,上面也说过了定义的是缓存到期的绝对时间。
一样,咱们也能够在html文件里直接使用:
<meta http-equiv="expires" content="Thu, 30 Nov 2017 11:17:26 GMT">
复制代码
若是设置的是已通过去的时间会怎样呢?YES!!!则刷新页面会从新发送请求。
**Pragma禁用缓存,若是又给Expires定义一个还未到期的时间,那么Pragma字段的优先级会更高。**🤖
🤖Expires有一个很大的弊端,就是它返回的是服务器的时间,但判断的时候用的倒是客户端的时间,这就致使Expires很被动,由于用户有可能改变客户端的时间,致使缓存时间判断出错,这也是引入Cache-Control:max-age
指令的缘由之一。
接下来这几个字段都是校验字段,或者说是在协商缓存阶段发挥做用的字段。第一个就是Last-modified,这个字段不光协商缓存起做用,在启发式缓存阶段一样起到相当重要的做用。
在浏览器第一次请求某一个URL时,服务器端的返回状态码会是200,响应的实体内容是客户端请求的资源,同时有一个Last-Modified
的属性标记此文件在服务器端最后被修改的时间。like this:
Last-Modified : Fri , 12 May 2006 18:53:33 GMT
复制代码
当浏览器第二次请求这个URL的时候,根据HTTP协议规定,浏览器会把第一次Last-Modified
的值存储在If-Modified-Since
里面发送给服务端来验证资源有没有修改。like this:
If-Modified-Since : Fri , 12 May 2006 18:53:33 GMT
复制代码
服务端经过If-Modified-Since
字段来判断在这两次访问期间资源有没有被修改过,从而决定是否返回完整的资源。若是有修改正常返回资源,状态码200,若是没有修改只返回响应头,状态码304,告知浏览器资源的本地缓存还可用。
用途:
这个字段字面意思和If-Modified-Since
相反,但处理方式并非相反的。若是文件在两次访问期间没有被修改则返回200和资源,若是文件修改了则返回状态码412(预处理错误)。
用途:
If-Range
消息头的范围请求搭配使用,实现断点续传的功能,即若是资源没修改继续下载,若是资源修改了,续传的意义就没有了。😈Last-Modified
有几个缺点:无法准确的判断资源是否真的修改了,好比某个文件在1秒内频繁更改了屡次,根据Last-Modified的时间(单位是秒)是判断不出来的,再好比,某个资源只是修改了,但实际内容并无发生变化,Last-Modified也没法判断出来,所以在HTTP/1.1中还推出了ETag
这个字段👇
服务器能够经过某种自定的算法对资源生成一个惟一的标识(好比md5标识),而后在浏览器第一次请求某一个URL时把这个标识放到响应头传到客户端。服务器端的返回状态会是200。
ETag: abc-123456
复制代码
ETag的值有可能包含一个 W/ 前缀,来提示应该采用弱比较算法(这个是多此一举,由于 If-None-Match 用且仅用这一算法)。🙄
If-None-Match和If-Modified-Since同时存在的时候If-None-Match优先级更高。
当浏览器第二次请求这个URL的时候,根据HTTP协议规定,浏览器回把第一次ETag的值存储在If-None-Match里面发送给服务端来验证资源有没有修改。like this:
If-None-Match: abc-123456
复制代码
Get请求中,当且仅当服务器上没有任何资源的ETag属性值与这个首部中列出的相匹配的时候,服务器端会才返回所请求的资源,响应码为200。若是没有资源的ETag值相匹配,那么返回304状态码。
POST、PUT等请求改变文件的请求,若是没有资源的ETag值相匹配,那么返回412状态码。
在请求方法为 GET) 和 HEAD的状况下,服务器仅在请求的资源知足此首部列出的 ETag
之一时才会返回资源。而对于 PUT或其余非安全方法来讲,只有在知足条件的状况下才能够将资源上传。
用途:
If-Match
首部能够用来避免更新丢失问题。它能够用来检测用户想要上传的不会覆盖获取原始资源以后作出的更新。若是请求的条件不知足,那么须要返回412(预处理错误) 响应。固然和Last-Modified
相比,ETag也有本身的缺点,好比因为须要对资源进行生成标识,性能方面就势必有所牺牲。😕
关于强校验和弱校验:
ETag 1 | ETag 2 | Strong Comparison | Weak Comparison |
---|---|---|---|
W/"1" | W/"1" | no match | match |
W/"1" | W/"2" | no match | no match |
W/"1" | "1" | no match | match |
"1" | "1" | match | match |
当Expires
和Cache-Control:max-age=xxx
同时存在的时候取决于缓存服务器应用的HTTP版本。应用HTTP/1.1版本的服务器会优先处理max-age,忽略Expires,而应用HTTP/1.0版本的缓存服务器则会优先处理Expires而忽略max-age。接下来看下和缓存服务器相关的两个字段。
Vary用来作什么的呢?试想这么一个场景:在某个网页中网站提供给移动端的内容是不一样的,怎么让缓存服务器区分移动端和PC端呢?不知道你是否注意,浏览器在每次请求都会携带UA字段来代表来源,因此咱们能够利用User-Agent
字段来区分不一样的客户端,用法以下:
Vary: User-Agent
复制代码
再好比,源服务器启用了gzip压缩,但用户使用了比较旧的浏览器,不支持压缩,缓存服务器如何返回?就能够这么设定:
Vary: Accept-Encoding
复制代码
固然,也能够这么用:
Vary: User-Agent, Accept-Encoding
复制代码
这意味着缓存服务器会以User-Agent
和 Accept-Encoding
两个请求首部字段来区分缓存版本。根据请求头里的这两个字段来决定返回给客户端什么内容。
这个字段说的是资源在缓存服务器存在的时长,前面也说了Cache-Control: max-age=[秒]
就是Age的最大值。
这个字段存在的意义是什么呢?用来区分请求的资源来自源服务器仍是缓存服务器的缓存的。
🤧但得结合另外一个字段来进行判断,就是Date,Date是报文建立的时间。
若是按F5频繁刷新发现响应里的Date没有改变,就说明命中了缓存服务器的缓存如下面的一个响应为🍐:
Accept-Ranges: bytes
Age: 1016859
Cache-Control: max-age=2592000
Content-Length: 14119
Content-Type: image/png
Date: Fri, 01 Dec 2017 12:27:25 GMT
ETag: "5912bfd0-3727"
Expires: Tue, 19 Dec 2017 17:59:46 GMT
Last-Modified: Wed, 10 May 2017 07:22:56 GMT
Ohc-Response-Time: 1 0 0 0 0 0
Server: bfe/1.0.8.13-sslpool-patch
复制代码
如上图来自百度首页某个图片的响应字段。咱们能够看到Age=1016859,说明这个资源已经在缓存服务器存在了1016859秒。若是文件被修改或替换,Age会从新由0开始累计。
Age消息头的值一般接近于0。表示此消息对象刚刚从原始服务器获取不久;其余的值则是表示代理服务器当前的系统时间与此应答消息中的通用消息头 Date的值之差。
上面这个结论归结为一个等式就是:
静态资源Age + 静态资源Date = 原服务端Date
复制代码
搜索了好久有没有关于这方面的权威总结,最后居然在百度百科找到了也是很惊讶,我本身加了一条用户强制刷新操做浏览器的反应。强制刷新,window下是Ctrl+F5
,mac下就是command+shift+R
操做了。:relieved:
操做 | 说明 |
---|---|
打开新窗口 | 若是指定cache-control的值为private、no-cache、must-revalidate,那么打开新窗口访问时都会从新访问服务器。而若是指定了max-age值,那么在此值内的时间里就不会从新访问服务器,例如:Cache-control: max-age=5 表示当访问此网页后的5秒内不会去再次访问服务器. |
在地址栏回车 | 若是值为private或must-revalidate,则只有第一次访问时会访问服务器,之后就再也不访问。若是值为no-cache,那么每次都会访问。若是值为max-age,则在过时以前不会重复访问。 |
按后退按扭 | 若是值为private、must-revalidate、max-age,则不会重访问,而若是为no-cache,则每次都重复访问. |
按刷新按扭 | 不管为什么值,都会重复访问.(可能返回状态码:200、304,这个不一样浏览器处理是不同的,FireFox正常,Chrome则会启用缓存(200 from cache)) |
按强制刷新按钮 | 当作首次进入从新请求(返回状态码200) |
:wink:若是想在浏览器点击“刷新”按钮的时候不让浏览器去发新的验证请求呢?办法找到一个,知乎上面一个回答,在页面加载完毕后经过脚本动态地添加资源:
$(window).load(function() {
var bg='http://img.infinitynewtab.com/wallpaper/100.jpg';
setTimeout(function() {
$('#bgOut').css('background-image', 'url('+bg+')');
},0);
});
复制代码
这部分准备的说应该叫离线存储。如今比较广泛用的是Appcache
,但Appcache
已经从web标准移除了,在可预见的将来里,ServiceWorker
可能会是一个比较适合的解决方案。
这是HTML5的一个新特性,经过离线存储达到用户在没有网络链接的状况下也能访问页面的功能。离线状态下即便用户点击刷新都能正常加载文档。
使用方法以下,在HTML文件中引入appcache
文件:
<!DOCTYPE html>
<html manifest="manifest.appcache">
<head>
<meta charset="UTF-8">
<title>***</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
复制代码
🤠web 应用中的 manifest
特性能够指定为缓存清单文件的相对路径或一个绝对 URL(绝对 URL 必须与应用同源)。缓存清单文件可使用任意扩展名,但传输它的 MIME 类型必须为 text/cache-manifest。
**注意:**在 Apache 服务器上,若要设置适用于清单(.appcache)文件的 MIME 类型,能够向根目录或应用的同级目录下的一个 .htaccess 文件中增长
AddType text/cache-manifest .appcache
。
CACHE MANIFEST
# 注释:须要缓存的文件,不管在线与否,均从缓存里读取
# v1 2017-11-30
# This is another comment
/static/logo.png
# 注释:不缓存的文件,始终从网络获取
NETWORK:
example.js
# 注释:获取不到资源时的备选路径,如index.html访问失败,则返回404页面
FALLBACK:
index.html 404.html
复制代码
上面就是一个完整的缓存清单文件的示例。
**注意:**主页必定会被缓存起来的,由于AppCache主要是用来作离线应用的,若是主页不缓存就没法离线查看了,所以把index.html添加到NETWORK中是不起效果的。
实际上这个特性已经web标准中删除,但如今为止还有不少浏览器支持它,因此这里提一下。
你能够用最新的Firefox(版本 57.0.1)测试下,控制台会有这么一行字👉:
程序缓存 API(AppCache)已不同意使用,几天后将被移除。需离线支持请尝试使用 Service Worker。
最新Chrome(版本 62.0.3202.94)却是没有这个警告。🐻
AppCache
之因此不受待见我想了下面几个缘由:
Service worker仍是一个实验性的功能,线上环境不推荐使用。🐒这里大概介绍一下。
Service worker本质上充当Web应用程序与浏览器之间的代理服务器。
🙂首先讲个小故事:
咱们都知道浏览器的js引擎处理js是单线程的,它就好像一个大Boss高高在上,同一个时间它只作一个事情(就是那么傲娇),基于这个弊端,W3C(HR)给大Boss招聘了一个秘书(web worker
),大Boss能够把琐碎的事情交给秘书web worker
去作,作完了发个微信(postMessage
)通知大Boss,大Boss经过onmessage
来获取秘书web worker
作的事情的结果。傍晚时分,下班时间到!大Boss回家哄儿子了,秘书也出去约会去了,没人加班了!这怎么行!W3C(HR)又提出了招个程序🐵的想法的想法,OK,Service Worker
应聘成功!因而,程序🙈就坚持在工做岗位上了,今后开启没完没了的加班之路。总的来讲这只猿的工做是这样的:
注意:Service workers之因此优于之前同类尝试(如上面提到的AppCache)),是由于它们没法支持当操做出错时终止操做。Service workers能够更细致地控制每一件事情。如何控制的呢?
Service workers利用了ES6中比较重要的特性Promise,而且在拦截请求的时候使用的是新的fetch API,之因此使用fetch就是由于fetch返回的是Promise对象。能够说Service workers重要组成部分就是三块:事件、Promise和Fetch请求。OK,talk is cheap,show you the code。🤓
首先咱们看下app.js文件:告诉浏览器注册某个JavaScript文件为service worker,检查service worker API是否可用,若是可用就注册service worker:
//使用 ServiceWorkerContainer.register()方法首次注册service worker。
if (navigator.serviceWorker) {
navigator.serviceWorker.register('./sw.js', {scope: './'})
.then(function (registration) {
console.log(registration);
})
.catch(function (e) {
console.error(e);
});
} else {
console.log('该浏览器不支持Service Worker');
}
复制代码
再来看看具体做为service worker的文件sw.js,例子以下:
const CACHE_VERSION = 'v1'; // 缓存文件的版本
const CACHE_FILES = [ // 须要缓存的文件
'./test.js',
'./app.js',
'https://code.jquery.com/jquery-3.0.0.min.js'
];
self.addEventListener('install', function (event) { // 监听worker的install事件
event.waitUntil( // 延迟install事件直到缓存初始化完成
caches.open(CACHE_VERSION)
.then(function (cache) {
console.log('缓存打开');
return cache.addAll(CACHE_FILES);
})
);
});
self.addEventListener('activate', function(event) {// 监听worker的activate事件
event.waitUntil(// 延迟activate事件直到
caches.keys().then(function(keys) {
return Promise.all(keys.map(function(key, i){
if(key !== CACHE_VERSION){
return caches.delete(keys[i]); // 清除旧版本缓存
}
}))
})
)
});
self.addEventListener('fetch', function(event) { // 截取页面的资源请求
event.respondWith(
caches.match(event.request).then(function(res) { // 判断缓存是否命中
if (res) { // 返回缓存中的资源
return res;
}
_request(event); // 执行请求备份操做
})
)
});
function _request(event) {
var url = event.request.clone();
return fetch(url).then(function(res) {// 使用fetch请求线上资源
// 错误判断
if (!res || res.status !== 200 || res.type !== 'basic') {
return res;
}
var response = res.clone(); // 建立了一个响应对象的克隆,储藏在一个单独的变量中
caches.open(CACHE_VERSION).then(function(cache) {// 缓存从线上获取的资源
cache.put(event.request, response);
});
return res;
})
}
复制代码
清除一个Service Worker也很简单:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js', {scope: './'}).then(function(registration) {
// registration worked
console.log('Registration succeeded.');
registration.unregister().then(function(boolean) {
// if boolean = true, unregister is successful
});
}).catch(function(error) {
// registration failed
console.log('Registration failed with ' + error);
});
};
复制代码
相对AppCache来讲,Service Worker的API增多了很多,用法也更复杂了些,但看得出Service Worker才是将来,对于web app来讲,更是如虎添翼。如今支持Service Worker的浏览器除了Chrome和Firefox,最近新添一个生力军——Safari也支持Service Worker了。期待它在将来大放异彩吧。🤗
以下,使用node原生代码简单的模拟下服务器发送响应的过程,包括对于协商缓存的处理过程:
var http = require('http');
var fs = require('fs');
var url = require('url');
process.env.TZ = 'Europe/London';
let tag = '123456';
http.createServer( function (request, response) {
var pathname = url.parse(request.url).pathname;
console.log("Request for " + pathname + " received.");
const fileMap = {
'js': 'application/javascript; charset=utf-8',
'html': 'text/html',
'png': 'image/png',
'jpg': 'image/jpeg',
'gif': 'image/gif',
'ico': 'image/*',
'appcache': 'text/cache-manifest'
}
fs.readFile(pathname.substr(1), function (err, data) {
if (request.headers['if-none-match'] === tag) {
response.writeHead(304, {
'Content-Type': fileMap[pathname.substr(1).split('.')[1]],
'Expires': new Date(Date.now() + 30000),
'Cache-Control': 'max-age=10, public',
'ETag': tag,
'Last-Modified': new Date(Date.now() - 30000),
'Vary': 'User-Agent'
});
} else {
response.writeHead(200, {
'Content-Type': fileMap[pathname.substr(1).split('.')[1]],
'Cache-Control': 'max-age=10, public',
'Expires': new Date(Date.now() + 30000),
'ETag': tag,
'Last-Modified': new Date(Date.now() - 30000),
'Vary': 'User-Agent'
});
response.write(fs.readFileSync(pathname.substr(1)));
}
response.end();
});
}).listen(8081);
复制代码
如上代码。若是你没使用过node,拷贝下代码存为file.js,安装node,命令行输入node file.js
,能够在同目录下创建index.html文件,在html文件中引用一些图片,CSS等文件,浏览器输入localhost:8081/index.html
进行模拟。🤓
1. 问题:请求被缓存,致使新代码未生效
解决方案:
Cache-Control:no-cache,must-revalidate
指令;If-modified-since:0
或If-none-match
;2. 问题:服务端缓存致使本地代码未更新
解决方案:
3. 问题: Cache-Control: max-age=0 和 no-cache有什么不一样
回答:
max-age=0
和no-cache
应该是从语气上不一样。max-age=0
是告诉客户端资源的缓存到期应该向服务器验证缓存的有效性。而no-cache
则告诉客户端使用缓存前必须向服务器验证缓存的有效性。
参考文档: