先看一张经典的流程图,结合理解html
吃了它~前端
数据传输
,节省了网费。性能
速度
主要就是在浏览器本地把对应的 IP 和域名关联起来,这样在进行 DNS 解析的时候就很快。git
是指存在内存中的缓存。从优先级上来讲,它是浏览器最早尝试去命中的一种缓存。从效率上来讲,它是响应速度最快的一种缓存。 内存缓存是快的,也是“短命”的。它和渲染进程“生死相依”,当进程结束后,也就是 tab 关闭之后,内存里的数据也将不复存在。github
浏览器缓存,也称Http 缓存,分为强缓存和协商缓存。优先级较高的是强缓存,在命中强缓存失败的状况下,才会走协商缓存浏览器
强缓存是利用 http
头中的 Expires
和 Cache-Control
两个字段来控制的。强缓存中,当请求再次发出时,浏览器会根据其中的 Expires
和 cache-control
判断目标资源是否“命中”强缓存,若命中则直接从缓存中获取资源,不会再与服务端发生通讯。缓存
实现强缓存,过去咱们一直用Expires
。当服务器返回响应时,在 Response Headers 中将过时时间写入 Expires
字段。像这样服务器
expires: Wed, 12 Sep 2019 06:12:18 GMT
markdown
能够看到,expires
是一个时间戳,接下来若是咱们试图再次向服务器请求资源,浏览器就会先对比本地时间和 expires
的时间戳,若是本地时间小于 expires
设定的过时时间,那么就直接去缓存中取这个资源。网络
从这样的描述中你们也不难猜想,expires
是有问题的,它最大的问题在于对本地时间
的依赖。若是服务端和客户端的时间设置可能不一样,或者我直接手动去把客户端的时间改掉,那么 expires
将没法达到咱们的预期。session
考虑到 expires
的局限性,HTTP1.1
新增了Cache-Control
字段来完成 expires
的任务。expires
能作的事情,Cache-Control
都能作;expires
完成不了的事情,Cache-Control
也能作。所以,Cache-Control
能够视做是 expires
的彻底替代方案。在当下的前端实践里,咱们继续使用 expires
的惟一目的就是向下兼容。
在 Cache-Control
中,咱们经过max-age
来控制资源的有效期。max-age
不是一个时间戳,而是一个时间长度。在本例中,max-age
是 31536000
秒,它意味着该资源在 31536000
秒之内都是有效的,完美地规避了时间戳带来的潜在问题。
Cache-Control
相对于 expires 更加准确,它的优先级也更高。当 Cache-Control
与 expires 同时出现时,咱们以 Cache-Control
为准。
能够参考下下面两张图:
协商缓存依赖于服务端与浏览器之间的通讯。协商缓存机制下,浏览器须要向服务器去询问缓存的相关信息,进而判断是从新发起请求、下载完整的响应,仍是从本地获取缓存的资源。若是服务端提示缓存资源未改动(Not Modified),资源会被重定向到浏览器缓存,这种状况下网络请求对应的状态码是 304。
协商缓存的实现,从 Last-Modified
到 Etag
,Last-Modified
是一个时间戳,若是咱们启用了协商缓存,它会在首次请求时随着 Response Headers
返回:
Last-Modified: Fri, 27 Oct 2017 06:35:57 GMT
复制代码
随后咱们每次请求时,浏览器的请求头 headers
会带上一个叫 If-Modified-Since
的时间戳字段,它的值正是上一次 response
返回给它的 Last-Modified
值:
If-Modified-Since: Fri, 27 Oct 2017 06:35:57 GMT
复制代码
服务器接收到这个时间戳后,会比对该时间戳和资源在服务器上的最后修改时间是否一致,从而判断资源是否发生了变化。若是发生了变化,就会返回一个完整的响应内容,并在 Response Headers
中添加新的 Last-Modified
值;不然,返回 304 响应,Response Headers
不会再添加 Last-Modified
字段。
以下图:
经过最后修改时间来判断缓存是否可用
Last-Modified
:响应时告诉客户端此资源的最后修改时间If-Modified-Since
:当资源过时时(使用 Cache-Control
标识的 max-age
),发现资源具备 Last-Modified
声明,则再次向服务器请求时带上头 If-Modified-Since
。If-Modified-Since
则与被请求资源的最后修改时间进行比对。若最后修改时间较新,说明资源又被改动过,则响应最新的资源内容并返回 200
状态码;If-Modified-Since
同样,说明资源没有修改,则响应 304
表示未更新,告知浏览器继续使用所保存的缓存文件。看个实例代码:
let http = require('http'); let fs = require('fs'); let path = require('path'); let mime = require('mime'); http.createServer(function (req, res) { let file = path.join(__dirname, req.url); fs.stat(file, (err, stat) => { if (err) { sendError(err, req, res, file, stat); } else { let ifModifiedSince = req.headers['if-modified-since']; if (ifModifiedSince) { if (ifModifiedSince == stat.ctime.toGMTString()) { res.writeHead(304); res.end(); } else { send(req, res, file, stat); } } else { send(req, res, file, stat); } } }); }).listen(8080); function send(req, res, file, stat) { res.setHeader('Last-Modified', stat.ctime.toGMTString()); res.writeHead(200, { 'Content-Type': mime.getType(file) }); fs.createReadStream(file).pipe(res); } function sendError(err, req, res, file, stat) { res.writeHead(400, { "Content-Type": 'text/html' }); res.end(err ? err.toString() : "Not Found"); 复制代码
使用 Last-Modified 存在一些弊端,这其中最多见的就是这样几个场景 1. 某些服务器不能精确获得文件的最后修改时间, 这样就没法经过最后修改时间来判断文件是否更新了。 2. 咱们编辑了文件,但文件的内容没有改变。服务端并不清楚咱们是否真正改变了文件,它仍然经过最后编辑时间进行判断。所以这个资源在再次被请求时,会被当作新资源,进而引起一次完整的响应——不应从新请求的时候,也会从新请求。 3. 当咱们修改文件的速度过快时(好比花了 100ms
完成了改动),因为 If-Modified-Since
只能检查到以秒为最小计量单位的时间差,因此它是感知不到这个改动的——该从新请求的时候,反而没有从新请求了。 4. 若是一样的一个文件位于多个CDN服务器上的时候内容虽然同样,修改时间不同。
第二和第三这两个场景其实指向了同一个 bug——服务器并无正确感知文件的变化。为了解决这样的问题,Etag 做为 Last-Modified
的补充出现了。
这个是协商缓存中的另一种
Etag
是由服务器为每一个资源生成的惟一的标识字符串(指纹),这个标识字符串能够是基于文件内容编码的,只要文件内容不一样,它们对应的 Etag
就是不一样的,反之亦然。所以 Etag
可以精准地感知文件的变化。
Etag
是 Web 服务端产生的,而后发给浏览器客户端。生成过程须要服务器额外付出开销,会影响服务端的性能,这是它的弊端。所以启用 Etag
须要咱们审时度势。正如咱们刚刚所提到的——Etag
并不能替代 Last-Modified
,它只能做为 Last-Modified
的补充和强化存在。
执行流程是这样的: 1. 客户端想判断缓存是否可用能够先获取缓存中文档的ETag
,而后经过If-None-Match
发送请求给 Web 服务器询问此缓存是否可用。 2. 服务器收到请求,将服务器的中此文件的ETag
,跟请求头中的If-None-Match
相比较,若是值是同样的,说明缓存仍是最新的,Web 服务器将发送304 Not Modified
响应码给客户端表示缓存未修改过,可使用。 3. 若是不同则 Web 服务器将发送该文档的最新版本给浏览器客户端
看以下实例代码:
let http = require("http"); let fs = require("fs"); let path = require("path"); let mime = require("mime"); let crypto = require("crypto"); http .createServer(function(req, res) { let file = path.join(__dirname, req.url); fs.stat(file, (err, stat) => { if (err) { sendError(err, req, res, file, stat); } else { let ifNoneMatch = req.headers["if-none-match"]; let etag = crypto .createHash("sha1") .update(stat.ctime.toGMTString() + stat.size) .digest("hex"); if (ifNoneMatch) { if (ifNoneMatch == etag) { res.writeHead(304); res.end(); } else { send(req, res, file, etag); } } else { send(req, res, file, etag); } } }); }) .listen(8080); function send(req, res, file, etag) { res.setHeader("ETag", etag); res.writeHead(200, { "Content-Type": mime.lookup(file) }); fs.createReadStream(file).pipe(res); } function sendError(err, req, res, file, etag) { res.writeHead(400, { "Content-Type": "text/html" }); res.end(err ? err.toString() : "Not Found"); } 复制代码
优先级:
Etag
在感知文件变化上比 Last-Modified
更加准确,优先级也更高。当 Etag
和 Last-Modified
同时存在时,以 Etag
为准。
对比:
Service Worker
是一种独立于主线程以外的 Javascript 线程
。它脱离于浏览器窗体,所以没法直接访问 DOM。这样独立的个性使得
Service Worker
的“我的行为”没法干扰页面的性能,这个幕后工做者能够帮咱们实现离线缓存、消息推送和网络代理等功能。咱们借助 Service worker
实现的离线缓存就称为 Service Worker
Cache。
Service Worker
的生命周期包括 install、activited、working
三个阶段。一旦 Service Worker
被 install,它将始终存在,只会在 active 与 working 之间切换,除非咱们主动终止它。这是它能够用来实现离线存储的重要先决条件.
它就在浏览器开发工具(F12) Application
标签页中
Push Cache
是指 HTTP2
在 server push
阶段存在的缓存。这块的知识比较新,应用也还处于萌芽阶段,应用范围有限不表明不重要——HTTP2
是趋势、是将来。在它还未被推而广之的此时此刻,仍但愿你们能对 Push Cache
的关键特性有所了解:
Push Cache
是缓存的最后一道防线。浏览器只有在 Memory Cache
、HTTP Cache
和 Service Worker Cache
均未命中的状况下才会去询问 Push Cache
。Push Cache
是一种存在于会话阶段的缓存,当 session 终止时,缓存也随之释放。HTTP2
链接,那么它们就能够共享同一个 Push Cache
。走上面的缓存机制
Cache
目录,第二次请求时浏览器会先检查Cache
目录下是否含有该文件,若是有,而且还没到Expires
设置的时间,即文件尚未过时,那么此时浏览器将直接从 Cache 目录中读取文件,而再也不发送请求Expires
是服务器响应消息头字段,在响应 http 请求时告诉浏览器在过时时间前浏览器能够直接从浏览器缓存取数据,而无需再次请求,这是HTTP1.0
的内容,如今浏览器均默认使用HTTP1.1
,因此基本能够忽略Cache-Control
与Expires
的做用一致,都是指明当前资源的有效期,控制浏览器是否直接从浏览器缓存取数据仍是从新发请求到服务器取数据,若是同时设置的话,其优先级高于Expires
let http = require("http"); let fs = require("fs"); let path = require("path"); let mime = require("mime"); let crypto = require("crypto"); http .createServer(function(req, res) { let file = path.join(__dirname, req.url); console.log(file); fs.stat(file, (err, stat) => { if (err) { sendError(err, req, res, file, stat); } else { send(req, res, file); } }); }) .listen(8080); function send(req, res, file) { let expires = new Date(Date.now() + 60 * 1000); res.setHeader("Expires", expires.toUTCString()); res.setHeader("Cache-Control", "max-age=60"); res.writeHead(200, { "Content-Type": mime.lookup(file) }); fs.createReadStream(file).pipe(res); } function sendError(err, req, res, file, etag) { res.writeHead(400, { "Content-Type": "text/html" }); res.end(err ? err.toString() : "Not Found"); } 复制代码
若是本文对你有帮助的话,给本文点个赞吧
鄙人github,一块儿学习~