We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
浏览器缓存一直是个老生常谈的话题,也是面试官常常用来鉴别面试者的利器,作为前端来讲这块知识是属于必须掌握的,再者利用好缓存也是做性能优化的有效方法。本文将从缓存原因、缓存读写顺序,缓存位置以及缓存策略这几个角度介绍浏览器缓存,并且最后给出实践的应用举例。
很多同学知道缓存的位置和字段,知道怎么用,但是你有没有想过为什么我们的页面需要浏览器缓存呢?
当浏览器对一个资源(比如一个外链的 a.js)进行请求的时候会发生什么?请从缓存的角度大概说下:
a.js
Service Worker
fetch
memory cache
disk cache
no-store
Cache Storage
cache.put()
上面这一系列过程其实是浏览器查找缓存和把资源存入缓存的执行流程。这其中出现了很多专业词汇,让人看了一脸懵逼,下面将从缓存位置和缓存策略两个角度简要介绍浏览器的缓存。
从浏览器开发者工具的 Network 面板下某个请求的 Size 中可以看到当前请求资源的大小以及来源,从这些来源我们就知道该资源到底是从 memory cache 中读取的呢,还是从 disk cache 中读取的,亦或者是服务器返回的。而这些就是缓存位置:
Network
Size
是一个注册在指定源和路径下的事件驱动 worker;特点是:
worker
DOM
API
XHR
localStorage
HTTPS
说了这么多特点,那它和缓存有啥关系?其实它有一个功能就是离线缓存:Service Worker Cache;区别于浏览器内部的 memory cache 和 disk cache,它允许我们自己去操控缓存,具体操作过程可以参看 Using_Service_Workers;通过 Service Worker 设置的缓存会出现在浏览器开发者工具 Application 面板下的 Cache Storage 中。
Service Worker Cache
Application
是浏览器内存中的缓存,相比于 disk cache 它的特点是读取速度快,但容量小,且时效性短,一旦浏览器 tab 页关闭,memory cache 就将被清空。memory cache 会自动缓存所有资源嘛?答案肯定是否定的,当 HTTP 头设置了 Cache-Control: no-store 的时候或者浏览器设置了 Disabled cache 就无法把资源存入内存了,其实也无法存入硬盘。当从 memory cache 中查找缓存的时候,不仅仅会去匹配资源的 URL,还会看其 Content-type 是否相同。
tab
HTTP
Cache-Control: no-store
Disabled cache
URL
Content-type
也叫 HTTP cache 是存在硬盘中的缓存,根据 HTTP 头部的各类字段进行判定资源的缓存规则,比如是否可以缓存,什么时候过期,过期之后需要重新发起请求吗?相比于 memory cache 的 disk cache 拥有存储空间时间长等优点,网站中的绝大多数资源都是存在 disk cache 中的。
HTTP cache
浏览器如何判断一个资源是存入内存还是硬盘呢?关于这个问题,网上说法不一,不过比较靠谱的观点是:对于大文件大概率会存入硬盘中;当前系统内存使用率高的话,文件优先存入硬盘。
缓存按照缓存位置划分,其实还有一个 HTTP/2 的内容 push cache,由于目前国内对 HTTP/2 应用还不广泛,且网上对 push cache 的知识还不齐全,所以本篇不打算介绍这块,感兴趣的可以阅读这篇文章:HTTP/2 push is tougher than I thought
HTTP/2
push cache
根据 HTTP header 的字段又可以将缓存分成强缓存和协商缓存。强缓存可以直接从缓存中读取资源返回给浏览器而不需要向服务器发送请求,而协商缓存是当强缓存失效后(过了过期时间),浏览器需要携带缓存标识向服务器发送请求,服务器根据缓存标识决定是否使用缓存的过程。强缓存的字段有:Expires 和 Cache-Control。协商缓存的字段有:Last-Modified 和 ETag。
HTTP header
Expires
Cache-Control
Last-Modified
ETag
Expires 是 HTTP/1.0 的字段,表示缓存过期时间,它是一个 GMT 格式的时间字符串。Expires 需要在服务端配置(具体配置也根据服务器而定),浏览器会根据该过期日期与客户端时间对比,如果过期时间还没到,则会去缓存中读取该资源,如果已经到期了,则浏览器判断为该资源已经不新鲜要重新从服务端获取。由于 Expires 是一个绝对的时间,所以会局限于客户端时间的准确性,从而可能会出现浏览器判断缓存失效的问题。如下是一个 Expires 示例,是一个日期/时间:
HTTP/1.0
GMT
Expires: Wed, 21 Oct 2020 07:28:00 GMT
它是 HTTP/1.1 的字段,其中的包含的值很多:
HTTP/1.1
max-age
must-revalidate
public
private
no-cache
Cache-Control 的值是可以混合使用的,比如:
Cache-Control: private, max-age=0, no-cache
当混合使用的时候它们的优先级如下图所示:
当 Expires 和 Cache-Control 都被设置的时候,浏览器会优先考虑后者。当强缓存失效的时候,则会进入到协商缓存阶段。具体细节是这样:浏览器从本地查找强缓存,发现失效了,然后会拿着缓存标识请求服务器,服务器拿着这个缓存标识和对应的字段进行校验资源是否被修改,如果没有被修改则此时响应状态会是 304,且不会返回请求资源,而是直接从浏览器缓存中读取。
而浏览器缓存标识可以是:Last-Modified 和 ETag:
资源的最后修改时间;第一次请求的时候,响应头会返回该字段告知浏览器资源的最后一次修改时间;浏览器会将值和资源存在缓存中;再次请求该资源的时候,如果强缓存过期,则浏览器会设置请求头的 If-Modifined-Since 字段值为存储在缓存中的上次响应头 Last-Modified 的值,并且发送请求;服务器拿着 If-Modifined-Since 的值和 Last-Modified 进行对比。如果相等,表示资源未修改,响应 304;如果不相等,表示资源被修改,响应 200,且返回请求资源。如果资源更新的速度是小于 1 秒的,那么该字段将失效,因为 Last-Modified 时间是精确到秒的。所以有了 ETag。
If-Modifined-Since
根据资源内容生成的唯一标识,资源是否被修改的判断过程和上面的一致,只是对应的字段替换了。Last-Modified 替换成 ETag,If-Modifined-Since 替换成 If-None-Match。
If-None-Match
当 Last-Modified 和 ETag 都被设置的时候,浏览器会优先考虑后者。
TAB
Cache-control: no-cache
Pragma: no-cache
比如页面引入了一个 JQuery,对于页面来说这个脚本就是一个工具库,基本上是不会发生变化的,对于这种资源可以将它的缓存时间设置得长一点,比如如下这个地址的脚本:
JQuery
<script src="https://cdn.bootcss.com/jquery/2.1.4/jquery.min.js"></script>
你会看到它的响应头里设置了,max-age=2592000 直接缓存 30 天:
max-age=2592000
cache-control: public, max-age=2592000
对于频繁变化的资源,比如某个页面经常需要调整,那么这个页面就需要在每次请求的时候都进行验证,可以在响应头这样设置:
cache-control: no-cache
当然并不是所有请求都能被缓存,无法被浏览器缓存的请求如下:
Cache-Control: no-cache
pragma: no-cache(HTTP1.0)
Cache-Control: max-age=0
Cookie
POST
Last-Modified/Etag
Cache-Control/Expires
The text was updated successfully, but these errors were encountered:
No branches or pull requests
浏览器缓存一直是个老生常谈的话题,也是面试官常常用来鉴别面试者的利器,作为前端来讲这块知识是属于必须掌握的,再者利用好缓存也是做性能优化的有效方法。本文将从缓存原因、缓存读写顺序,缓存位置以及缓存策略这几个角度介绍浏览器缓存,并且最后给出实践的应用举例。
为什么要缓存
很多同学知道缓存的位置和字段,知道怎么用,但是你有没有想过为什么我们的页面需要浏览器缓存呢?
缓存读写顺序
当浏览器对一个资源(比如一个外链的
a.js
)进行请求的时候会发生什么?请从缓存的角度大概说下:Service Worker
的fetch
事件获取资源;memory cache
;disk cache
;这里又细分:disk cache
(如果请求头信息配置可以存的话);memory cache
(无视请求头信息的配置,除了no-store
之外);Service Worker
的Cache Storage
(如果Service Worker
的脚本调用了cache.put()
);上面这一系列过程其实是浏览器查找缓存和把资源存入缓存的执行流程。这其中出现了很多专业词汇,让人看了一脸懵逼,下面将从缓存位置和缓存策略两个角度简要介绍浏览器的缓存。
缓存位置
从浏览器开发者工具的
Network
面板下某个请求的Size
中可以看到当前请求资源的大小以及来源,从这些来源我们就知道该资源到底是从memory cache
中读取的呢,还是从disk cache
中读取的,亦或者是服务器返回的。而这些就是缓存位置:Service Worker
是一个注册在指定源和路径下的事件驱动
worker
;特点是:worker
上下文,因此它不能访问DOM
;API
(如XHR
和localStorage
)不能在Service Worker
中使用;HTTPS
环境下才可以使用;说了这么多特点,那它和缓存有啥关系?其实它有一个功能就是离线缓存:
Service Worker Cache
;区别于浏览器内部的memory cache
和disk cache
,它允许我们自己去操控缓存,具体操作过程可以参看 Using_Service_Workers;通过Service Worker
设置的缓存会出现在浏览器开发者工具Application
面板下的Cache Storage
中。memory cache
是浏览器内存中的缓存,相比于
disk cache
它的特点是读取速度快,但容量小,且时效性短,一旦浏览器tab
页关闭,memory cache
就将被清空。memory cache
会自动缓存所有资源嘛?答案肯定是否定的,当HTTP
头设置了Cache-Control: no-store
的时候或者浏览器设置了Disabled cache
就无法把资源存入内存了,其实也无法存入硬盘。当从memory cache
中查找缓存的时候,不仅仅会去匹配资源的URL
,还会看其Content-type
是否相同。disk cache
也叫
HTTP cache
是存在硬盘中的缓存,根据HTTP
头部的各类字段进行判定资源的缓存规则,比如是否可以缓存,什么时候过期,过期之后需要重新发起请求吗?相比于memory cache
的disk cache
拥有存储空间时间长等优点,网站中的绝大多数资源都是存在disk cache
中的。缓存按照缓存位置划分,其实还有一个
HTTP/2
的内容push cache
,由于目前国内对HTTP/2
应用还不广泛,且网上对push cache
的知识还不齐全,所以本篇不打算介绍这块,感兴趣的可以阅读这篇文章:HTTP/2 push is tougher than I thought缓存策略
根据
HTTP header
的字段又可以将缓存分成强缓存和协商缓存。强缓存可以直接从缓存中读取资源返回给浏览器而不需要向服务器发送请求,而协商缓存是当强缓存失效后(过了过期时间),浏览器需要携带缓存标识向服务器发送请求,服务器根据缓存标识决定是否使用缓存的过程。强缓存的字段有:Expires
和Cache-Control
。协商缓存的字段有:Last-Modified
和ETag
。Expires
Expires
是HTTP/1.0
的字段,表示缓存过期时间,它是一个GMT
格式的时间字符串。Expires
需要在服务端配置(具体配置也根据服务器而定),浏览器会根据该过期日期与客户端时间对比,如果过期时间还没到,则会去缓存中读取该资源,如果已经到期了,则浏览器判断为该资源已经不新鲜要重新从服务端获取。由于Expires
是一个绝对的时间,所以会局限于客户端时间的准确性,从而可能会出现浏览器判断缓存失效的问题。如下是一个Expires
示例,是一个日期/时间:Cache-Control
它是
HTTP/1.1
的字段,其中的包含的值很多:max-age
最大缓存时间,值的单位是秒,在该时间内,浏览器不需要向浏览器请求。这个设置解决了Expires
中由于客户端系统时间不准确而导致缓存失效的问题;must-revalidate
:如果超过了max-age
的时间,浏览器必须向服务器发送请求,验证资源是否还有效;public
响应可以被任何对象(客户端、代理服务器等)缓存;private
响应只能被客户端缓存;no-cache
跳过强缓存,直接进入协商缓存阶段;no-store
不缓存任何内容,设置了这个后资源也不会被缓存到内存和硬盘;Cache-Control
的值是可以混合使用的,比如:当混合使用的时候它们的优先级如下图所示:
当
Expires
和Cache-Control
都被设置的时候,浏览器会优先考虑后者。当强缓存失效的时候,则会进入到协商缓存阶段。具体细节是这样:浏览器从本地查找强缓存,发现失效了,然后会拿着缓存标识请求服务器,服务器拿着这个缓存标识和对应的字段进行校验资源是否被修改,如果没有被修改则此时响应状态会是 304,且不会返回请求资源,而是直接从浏览器缓存中读取。而浏览器缓存标识可以是:
Last-Modified
和ETag
:Last-Modified
资源的最后修改时间;第一次请求的时候,响应头会返回该字段告知浏览器资源的最后一次修改时间;浏览器会将值和资源存在缓存中;再次请求该资源的时候,如果强缓存过期,则浏览器会设置请求头的
If-Modifined-Since
字段值为存储在缓存中的上次响应头Last-Modified
的值,并且发送请求;服务器拿着If-Modifined-Since
的值和Last-Modified
进行对比。如果相等,表示资源未修改,响应 304;如果不相等,表示资源被修改,响应 200,且返回请求资源。如果资源更新的速度是小于 1 秒的,那么该字段将失效,因为Last-Modified
时间是精确到秒的。所以有了ETag
。ETag
根据资源内容生成的唯一标识,资源是否被修改的判断过程和上面的一致,只是对应的字段替换了。
Last-Modified
替换成ETag
,If-Modifined-Since
替换成If-None-Match
。当
Last-Modified
和ETag
都被设置的时候,浏览器会优先考虑后者。浏览器的行为
URL
后回车: 查找disk cache
中是否有匹配。如有则使用;如没有则发送网络请求。TAB
页并没有关闭,因此memory cache
是可用的,会被优先使用(如果匹配的话),其次才是disk cache
。Cache-control: no-cache
(为了兼容,还带了Pragma: no-cache
)。服务器直接返回 200 和最新内容。Network
面板下设置了Disabled cache
禁用缓存后,浏览器将不会从memory cache
或者disk cache
中读取缓存,而是直接发起网络请求。缓存应用
静态资源
比如页面引入了一个
JQuery
,对于页面来说这个脚本就是一个工具库,基本上是不会发生变化的,对于这种资源可以将它的缓存时间设置得长一点,比如如下这个地址的脚本:你会看到它的响应头里设置了,
max-age=2592000
直接缓存 30 天:频繁变化的资源
对于频繁变化的资源,比如某个页面经常需要调整,那么这个页面就需要在每次请求的时候都进行验证,可以在响应头这样设置:
不进行缓存
当然并不是所有请求都能被缓存,无法被浏览器缓存的请求如下:
HTTP
信息头中包含Cache-Control: no-cache
,pragma: no-cache(HTTP1.0)
,或Cache-Control: max-age=0
等告诉浏览器不用缓存的请求;Cookie
、认证信息等决定输入内容的动态请求是不能被缓存的;HTTPS
安全加密的请求;POST
请求无法被缓存;HTTP
响应头中不包含Last-Modified/Etag
,也不包含Cache-Control/Expires
的请求无法被缓存;参考文章
The text was updated successfully, but these errors were encountered: