前端缓存是我们前端开发中, 缓存是不可避免的知识点, 最近在复习缓存的知识, 整理了一下浏览器所有缓存类型, 希望供大家参考. 前端的缓存主要分两种, 第一种是网络缓存, 是前端通过网络请求到数据之后, 将数据保存到本地, 当下次再需要请求到相同的数据时, 直接从本地取. 第二种是数据缓存, 是前端通过js将页面执行过程中得到一些数据保存到浏览器中.
网络缓存
网络缓存主要分为在线缓存和离线缓存, 普通缓存又分为强缓存和协商缓存两种, 而离线缓存主要分为Cache Storage和Application Storage两种实现方式.
普通缓存
强缓存
强缓存是通过网络请求到数据时, 就已经确定了缓存的有效时间, 这种缓存一般用于图片等不会轻易发生修改的文件;
例如我们在请求一张图片时, 服务器在返回图片时, 就已经在response header中设置了图片的缓存有效时间, 一般通过Expires 和 Cache-Control 两个字段来设置;
Expires的值是一个固定的日期, 例如图片中的 'Thu, 09 Jan 2020 01:20:33 GMT' 就表示该图片在缓存有效时间到2020年1月9日01:20:33为止, 在这个时间之前请求就用本地缓存的这张图片, 超过这个时间, 则需要向服务器发送请求, 请求新的图片;
Cache-Control的值常见的有两个, 第一个是'no-cache', 表示不使用缓存, 第二个是'max-age=', 表示请求到数据之后, 多少时间内有效, 例如上图max-age=2592000, 就表示请求到图片后的2592000s内有效, 超过这个时间就需要重新发送请求;
如果返回的资源, 同时设有Cache-Control和Expires, 那么以Cache-Control值为准, 因为Expires设置的时间是固定日期, 存在着服务器和浏览器的时区不一致, 导致设置的时间不准确的情况, 所以Cache-Control的优先级大于Expires;
协商缓存
协商缓存是在强制缓存失效后, 浏览器向服务器发起请求, 判断是否继续使用当前的本地缓存的过程;
协商缓存的有效时间同样是由服务器设置, 服务器返回文件时, 就已经在response header中设置了协商缓存的校验时间, 一般通过etag 和 last-modified 两个字段来设置;
last-modified 的值是一个固定的日期, 表示该文件最后修改的时间, 例如上图中设置了'last-modified: Sat, 13 Dec 2019 14:19:52 GMT', 当下次访问服务器时, 就会在请求头中携带'if-modified-since: Sat, 13 Dec 2019 14:19:52 GMT', 询问服务器该时间之后是否修改了文件, 服务器通过对比该资源在服务器中的last-modified时间和携带过来的if-modified-since时间, 如果发生修改, 则直接返回新的资源, 同时携带新的last-modified, 此时状态码为200, 如果没有发生修改, 则直接返回304, 继续使用本地缓存的文件;
而另一个字段etag, 是服务器通过文件内容直接生成的hash值, 所以文件内容发生改变时, etag值也会发生改变, 例如上图的文件hash值为'ALgFrfzd_4_z0yjU_6yZZDqFo-_a', 那么向服务器发送请求时就会在请求头携带 'if-none-math: ALgFrfzd_4_z0yjU_6yZZDqFo-_a', 服务器取到该hash值和服务器中的文件的hash值, 进行比对, 如果两个值一样, 则表示两个文件资源的内容一样, 则没有发生改变, 则返回304, 继续使用本地缓存的文件, 如果两个hash值不一样, 则表示文件内容已经发生了改变, 那么则重新返回新的文件, 此时的状态码为200.
如果文件资源同时又etag和last-modified两个字段, 那么etag的优先级高于last-modified; 因为last-modified只能精确到秒, 如果某些文件在1s内发生多次修改, 那么last-modified就不准确了, 同时也可能存在文件内容没有发生改变, 但是last-modified却发生改变的情况, 所以etag的准确度高于last-modified.
总结
强缓存优先于协商缓存, 强缓存中的Cache-Control优先于Expires, 协商缓存中的etag优先于last-modified.
离线缓存
离线缓存指将页面资源缓存到本地, 使页面在离线环境下也可以进行访问. 离线缓存目前有两种实现方式, 一种manifest实现的离线缓存, 另一种是通过Service Worker实现的离线缓存
manifest
manifest方式实现的离线缓存, 就是通过在html的头部中引入一个.appcache为后缀的文件, 然后用.appcache文件中CACHE属性, 标识出需要缓存的文件, 那些文件就可以实现离线缓存 例如
<html manifest="a.appcache">...</html>,
appcache后缀的文件必须和html文件在同级目录下
.appcache文件的主要内容如下:
1、第一行是CACHE MANIFEST 这是必须需要的。
2、CACHE(必须) 标识出哪些文件需要缓存,相对路径/绝对路径。当第一次加载时,会被浏览器缓存在本地。
3、Network 这一部分是要绕过缓存直接读取的文件,可以使用通配符 ,大多数网站使用 * 。 当使用 时 表示出 CACHE指定文件外,其它所有页面都需要联网访问。
4、FALLBACK (可选) 当资源无法访问时,浏览器使用后备资源去替代。第二个表示后备页面。两个 URI 都必须使用相对路径并且与清单文件同源。可以使用通配符。
例如:
通过.appcache文件缓存下来的文件, 可以在谷歌的控制台中的application选项下的, cache中的application cache中查看, 下次访问这些资源时, 就不需要通过网络, 直接从application cache中取, 从而实现离线缓存
Service Worker
Service Worker是浏览器浏览器的一个进程, 可以将其理解为介于客户端和服务器之间的一个代理服务器, 在Service Worker中, 我们可以做很多事情, 比如拦截客户端的请求, 向客户端发送消息, 向服务器发送消息等等. 通过在Service Worker拦截客户端的请求, 用已缓存的资源返回给客户端, 从而实现离线缓存. 另外有一点需要注意的是,出于对安全问题的考虑,Service Worker 只能被使用在 https 或者本地的 localhost 环境下。顺便一提, PWA就是根据Service Worker实现离线缓存的.
Service Worker的注册方式如下, 在页面中运行以下js代码, 注册sw.js文件, sw.js即为Service Worker进程执行的代码
if ('serviceWorker' in window.navigator) {
navigator.serviceWorker.register('./sw.js', { scope: './' })
.then(function (reg) {
console.log('success', reg);
navigator.serviceWorker.controller && navigator.serviceWorker.controller.postMessage("this message is from page");
});
}
sw.js文件中, 我们就可以用this直接指代Service Worker对象, 然后我们通过给Service Worker监听fetch事件, 每当用户向服务器发送请求时, 这个事件就会被触发,
然后我们使用用户的请求对 Cache Stroage 进行匹配,如果匹配成功,则返回存储在缓存中的资源;如果匹配失败,则向服务器请求资源返回给用户,并使用 cache.put 方法把这些新的资源存储在缓存中Cache
通过Service Worker 缓存静态资源
this.addEventListener('install', function (event) {
event.waitUntil(
caches.open('sw_demo').then(function (cache) {
return cache.addAll([
'/style.css',
'/panda.jpg',
'./main.js'
])
}
));
}
当 Service Worker 在被安装的时候,我们能够对制定路径的资源进行缓存。CacheStroage 在浏览器中的接口名是 caches ,我们使用 caches.open 方法新建或打开一个已存在的缓存;cache.addAll 方法的作用是请求指定链接的资源并把它们存储到之前打开的缓存中。
但是上诉方法只能缓存固定的资源, 例如'/style.css','/panda.jpg','./main.js', 以下通过拦截所有请求, 将所有的用户的请求对 Cache Stroage 进行匹配,如果匹配成功,则返回存储在缓存中的资源;如果匹配失败,则向服务器请求资源返回给用户,并使用 cache.put 方法把这些新的资源存储在缓存中
this.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request).then(res => {
return res ||
fetch(event.request)
.then(responese => {
const responeseClone = responese.clone();
caches.open('sw_demo').then(cache => {
cache.put(event.request, responeseClone);
})
return responese;
})
.catch(err => {
console.log(err);
});
})
)
});
缓存在Cache Stroage中的资源可以通过谷歌的控制台中的application选项下的, cache中的Cache中 Storage查看, 下次访问这些资源时, 就不需要通过网络, 直接从Cache中 Storage中取, 从而实现离线缓存
数据缓存
Cookie
Cookie一般用于用户信息的存储, 大小一般不超过4KB, 一般由服务器设置. 服务器为了识别请求者的身份, 在返回的请求头设置, 例如'set-cookie: locale=zh-CN; path=/'
Cookie的主要值如下:
Name: cookie的值对应的别名, 取值时需要用到
Value: cookie的值
Domain: 域名, 即服务器所在的域名, 当访问的域名和该值一样时, 此cookie就会被一起携带到服务器
Path: 路径, 当域名匹配时, 再判断path是否与请求的url的路径是否一样, 通过设置path的值, 可以过滤同个域名下, 不需要携带到服务器的cookie值, 减少请求头的体积
Expires/max-age: cookie的有效时间
Size: cookie大大小
HttpOnly: 是否仅由服务器设置, 如果该值设置为true, 那么前端就无法通过js代码访问该cookie的值, 保证该cookie无法被修改, 从保证cookie的安全性
Secure: 安全性, 该值设置为true时, 则只有https协议时才会把cookie携带到服务器
SameSite: 跨站点限制, 可选值为'strict/lax/none', 通过设置跨站点的不发送cookie, 可以有效防止CSRF攻击
SessionStorage && LocalStorage
SessionStorage和LocalStorage是网页在运行时, 通过js保存到本地的数据, 大小一般不超过5m, SessionStorage与LocalStorage的区别在于SessionStorage在页面关闭时数据会消失, 而LocalStorage则会一直保存在本地.
WebSql && IndexedDB
上诉两种缓存我们都介绍了他们存储容量, cookie一般4KB左右, sessionstorage和localstorage一般5M, 如果前端需要存储到数据量比较大数据时, 就需要用到WebSql和IndexedDB; WebSql && IndexedDB 相当于后端的数据库, WebSql是关系型数据库, 类似于mysql, IndexedDB是非关系型数据库, 类似于mongodb; WebSql && IndexedDB的存储容量和硬盘大小直接挂钩, 可以满足我们存储大容量的数据, 例如用网易云信的sdk, 搭建的聊天室, 就是把聊天记录保存在IndexedDB中. 但是WebSql && IndexedDB目前只有在pc端的谷歌浏览器兼容性比较好, 上诉两种数据库的api大家可以去官网补充了解.
总结
上诉只是对各种缓存, 做了一个比较全的整理, 但并没有对各个缓存方式做太深入讲解, 大家可以通过以此作为线索, 对每个知识点做更加深入的研究, 希望对大家有帮助.
参考文献:
WebSql: https://www.runoob.com/html/html5-web-sql.html
IndexDB: http://www.ruanyifeng.com/blog/2018/07/indexeddb.html
Service Worker: https://blog.csdn.net/huangpb123/article/details/89498418
manifest: https://blog.csdn.net/wang_gongzi/article/details/82846309