不少时候,当打开浏览器的开发者工具,查看网络请求,对于资源大小(Size)选项,除了有具体的数字大小,还有from memory cache、from disk cache字段之类出现。javascript
这里就有不少疑问,这些字段表明着什么意思?这些字段又是谁来决定的?前端
从字面意思理解,大概也能猜到,这些字段表明着缓存位置。 按优先级,Size选项字段可分为:java
本质是做为服务器与客户端之间的代理服务器,伴随着PWA出现。Service Worker真正意义上将缓存控制权交给了前端,相比于LocalStorage、SessionStorage,后二者只是单纯的接口数据缓存,例如用户信息(一个对象)、列表信息(一个数组),而前者能够缓存静态资源,甚至拦截网络请求,根据网络情况做出不一样的缓存策略。固然,这不是本文讨论的重点。后端
顾名思义,这个是将资源缓存在了内存中。事实上,全部的网络请求都会被浏览器缓存到内存中,固然,内存容量有限,缓存不能无限存放在内存中,所以,注定是个短时间缓存。数组
内存缓存的控制权在浏览器,先后端都不能干涉。浏览器
与内存缓存相对的,这个是将资源缓存在硬盘中。虽然相比于内存,硬盘的读取速度要慢不少,但总比没有强。缓存
硬盘缓存的控制权在后端,经过什么控制呢?经过HTTP响应头控制,这是本文重点讨论的。性能优化
disk cache也叫http cahce,由于其严格遵照http响应头字段来判断哪些资源是否要被缓存,哪些资源是否已通过期。绝大多数缓存都是disk cache。服务器
disk cahce分为强制缓存与对比缓存。网络
控制强制缓存的有两种http响应头字段:
Expires: Fri, 08 Feb 2019 05:37:33 GMT
字段的值就表明了资源的过时时间,不过这个值是相对于客户端,而且客户端本地时间能够任意修改,所以这个字段并不可靠。Expires字段是Http 1.0的,Http 1.1 用Cache-Control字段替代它:
Cache-Control: max-age=2592000
Cache-control字段使用了绝对时间,单位为秒,即最大有效时间,在有效时间内,客户端直接从硬盘中读取资源。
看个例子,用Node.js搭建一个静态资源服务器,设置Cache-Control: max-age=2592000,每次请求都会被服务器打印出:
const server = http.createServer((req, res) => {
console.log(`收到请求,请求地址为: ${req.url}`);
fs.readFile(path.resolve(__dirname, './image.png'), (err, file) => {
if (err) {
res.end(err.message);
}
res.setHeader('Cache-control', 'max-age=2592000');
res.end(file);
});
}).listen(3000);
console.log('localhost:3000服务已开启!');
复制代码
第一次访问:
第二次访问:
能够看到,第一次请求,浏览器根据响应头中的Cache-Control字段,将资源缓存在硬盘中,第二次请求,浏览器直接从硬盘中读取资源,并无发送网络请求到服务器。
Cache-Control字段有如下可取值:
不一样于强制缓存,浏览器直接根据响应头Cache-Control字段直接判断缓存资源是否有效,对比缓存须要再次向服务器确认。
服务器经过响应头Last-Modified告知浏览器,资源最后被修改的时间:
Last-Modified: Fri, 08 Feb 2019 15:20:04 GMT
当再次请求该资源时,浏览器须要再次向服务器确认,资源是否过时,其中的凭证就是请求头If-Modified-Since字段,值为上次请求中响应头Last-Modified字段的值:
If-Modified-Since: Fri, 08 Feb 2019 15:20:04 GMT
服务器会接收If-Modified-Since字段的值与被请求资源的最后修改时间做比较
若是If-Modified-Since的值大于被请求资源的最后修改时间,则说明浏览器缓存的资源仍然有效,服务器会返回304状态码,告知浏览器直接取缓存便可。其中服务器返回的只有Http头部,并不包含主体(否则就没有缓存的意义了)。
不然,就跟正常的请求同样,服务器返回200状态码,并附带最新的资源。
看个例子,稍微修改下刚才的Node.js代码:
const server = http.createServer((req, res) => {
console.log(`收到请求,请求地址为: ${req.url}`);
const filename = path.resolve(__dirname, './image.png');
fs.stat(filename, (err, stat) => {
const lastModified = stat.mtime.toUTCString();
if (lastModified === req.headers['if-modified-since']) {
res.writeHead(304, 'Not Modified');
res.end();
}
else {
fs.readFile(filename, (err, file) => {
if (err) {
res.end(err.message);
}
res.setHeader('Last-Modified', lastModified);
res.end(file);
});
}
});
}).listen(3000);
console.log('localhost:3000服务已开启!');
复制代码
第一次请求:
第二次请求:
比对两次请求能够看到,除了状态码变成了304,资源大小也从57.8K降到了90B,这也证实响应中不包含http主体。
Last-Modiflied与Expires同样,也是有缺陷的。若是,资源的变化的时间间隔小于秒级,好比说是毫秒级的,或者说资源直接是动态生成的,那根据Last-Modified判断,资源就是每时每刻都最新的,即被修改过!
因此,Etag & If-Node-Match 就是来解决这个问题的。
Etag字段的值为文件的特殊标识,通常都是hash生成的,服务器存储着资源的Etag值。接下来的流程都与Lst-Modified & If-Modified-Since一致,只不过,比较的值从最后修改时间变成了Etag值。
Etag的优势在于,对于动态资源或者如今流行的Restful API返回的JSON数据,这些是没有修改时间这一说法的,可是Http标准并无规定Etag值如何生成,所以咱们经过代码本身生成Etag值。固然,计算Etag值会消耗服务器性能。
强制缓存与对比缓存是能够同时存在的,而且强制缓存的优先级高于对比缓存。实际应用中,也是二者共同使用的。
看个例子,在响应头中同时加上Cache-Control与Last-Modified:
res.setHeader('Cache-control', 'max-age=600');
res.setHeader('Last-Modified', lastModified);
复制代码
第一次请求:
第二次请求:
能够看到,虽然有Last-Modified字段,但仍是直接从硬盘中获取资源。
Http缓存策略,其实只是前端缓存的一小部分,但零乱的知识点仍是很是多的。最终处理缓存仍是浏览器,各浏览器的处理方式可能有差别,实际应用中仍是要慎重考虑。
合理运用Http缓存,对前端性能优化仍是很是有帮助的!