借助nodejs来彻底搞懂浏览器缓存 Published on Dec 3, 2020 in javascript with 0 comment ## 借助nodejs来彻底搞懂浏览器缓存 ### 强缓存 浏览器在命中强缓存的时候不会向服务器发请求,而是直接从本地缓存里取,关于强缓存对应的响应头字段有 `Expires` 和 `Cache-Control` > Expires是HTTP1.0时期的产物,Cache-Control是HTTP1.1时期的产物 ### Expires Expires设置缓存的有效期至某个时间点 例如: ```js const http = require('http') http.createServer((request, response) => { if (request.url == '/') { response.writeHeader(200, { 'Content-Type': 'text/html', 'Cache-Control': 'public, max-age=100'// http经过的任何地方都可以进行缓存(代理服务器也可以缓存),缓存时长是100秒,注意:不是毫秒! }) response.end('Cache') } else if (request.url == '/a.js') { response.writeHeader(200, { 'Content-Type': 'text/javascript', 'Expires': new Date('2090-01-01 22:22:22'), // HTTP1.0 'Cache-Control': 'public, max-age=100' // HTTP1.1 }) response.end('console.log("script loaded")') } }).listen(3000); ``` 接着我们可以打开浏览器测试,打开浏览器调试工具,切换到 `Network` 面板,找到 `size`这一列。 第一次进去的时候会显示 `a.js` 文件大小,再刷新的时候这里就会变成 `(memory cache)`,说明在第二次访问的时候这里就已经开始走缓存了 ### Cache-Control Cache-Control可以在请求头或者响应头中设置,可取值如下: 1. 可缓存性 * public: http经过的任何地方都可以进行缓存(代理服务器也可以缓存) * private: 只有发起请求的这个浏览器才可以缓存,如果设置了代理缓存,则代理缓存不会生效 * no-cache:任何一个节点都不可以缓存(绕过强缓存,但是还会经过协商缓存) 2. 到期 * max-age=: 设置缓存多少秒后过期 * s-maxage=: 会替代max-age,只有在代理服务器才会生效 * max-stale=: 是发起请求方主动带起的一个请求头,代表即便缓存过期,但是在max-stale这个时间内还可以使用过期的缓存,而不需要向服务器请求新的内容 3. 重新验证 * must-revalidate:如果max-age设置的内容过期,必须要向服务器请求重新获取数据验证内容是否过期 * proxy-revalidate: 主要用在缓存服务器,指定缓存服务器过期后重新从原服务器获取,不能从本地获取 4. 其它 * no-store: 本地和代理服务器都不可以缓存这个缓存,永远都要从服务器拿新的内容(强缓存,协商缓存都不会经过) * no-transform: 主要用户proxy服务器,告诉代理服务器不要随意改动返回的内容 我们还是在上面的例子的基础上修改一下 ```js // ..... response.writeHeader(200, { 'Content-Type': 'text/javascript', 'Cache-Control': 'max-age=20' // 20秒后过期 }); // ..... ``` > 当`Expires` 和 `Cache-Control`同时存在的时候,浏览器只会考虑Cache-Control ### 协商缓存 协商缓存也可以说是对比缓存,会对服务器发起请求来判断是否需要从服务器获取新的文件,协商缓存响应的响应头有 `Last-Modified`,`ETag` ### Last-Modified `Last-Modified`在服务端的响应头里面返回,对应的是文件的修改时间。浏览器端接收到这个响应头之后在下次请求这个文件时就会在请求头里带上`If-Modified-Since`字段来进行协商缓存(比对缓存) 现在我们来修改node代码进行检验: 我们在返回的dom里新增一个script标签,引入`b.js`,再新增一个路由判断 ```js const mTime = new Date(Date.now() + 1000 * 60 * 10) // …… else if (request.url === '/b.js') { response.writeHeader(200, { 'Content-Type': 'application/javascript; charset=UTF-8', 'Last-Modified': mTime }) response.end("console.log('引入了b文件')") } ``` 现在已经可以看到效果了,而且当再次刷新浏览器的时候,请求b.js文件的请求头里也带上了If-Modified-Since字段,值就是上次响应头里Last-Modified的值 其实这一步我们会发现一个小细节,在我们设置的这个时间范围内,再次请求b.js文件的时候还是从服务器那边获取的,并没有从缓存获取(从浏览器的size栏里可以看到) 其实这里我们有个比对的操作没有处理,下面我们把它加上 ```js // …… const requestMtime = request.headers['if-modified-since'] if (mTime == requestMtime) { // response.statusCode = 304 response.writeHeader(304, { 'Content-Type': 'application/javascript; charset=UTF-8', 'Last-Modified': mTime }) response.end() return } // …… ``` 测试了几下,害,又跟预期的一样 ### ETag 接着,我们来测试 `ETag`,我们在node上添加响应的代码 ```js // …… response.writeHeader(200, { 'ETag': 'abcd' }); // …… ``` 这里我们给ETag随便设置的abcd,发现浏览器在下次请求的时候会增加`If-None-Match`字段,且值是abcd > 当`Last-Modified` 和 `ETag`同时出现时,浏览器只会考虑`ETag` * 弱ETag(Weak Etag),这个Etag仅仅基于MTime(修改时间)来生成,以W/开始,比如:W/"2e681a" * 强ETag,默认的Apache的FileEtag设置为: FileEtag INode MtimeSize,也就是根据这三个属性来生成Etag值,他们之间通过一些算法来实现,并输出成hex的格式,相邻属性之间用-分隔,比如:Etag "2e681a-6-5d044840",这里面的三个段,分别代表了INode,MTime,Size根据算法算出的值的Hex格式 ## 浏览器的不同刷新的请求过程 1. 地址栏输入url回车,如果浏览器发现缓存中有这个文件,就会直接走缓存(最快),(会走强缓存) 2. F5,浏览器会走协商缓存,跳过强缓存 3. ctrl+F5 删除缓存文件,然后去服务器请求完成的资源,请求头里会有`Pragma: no-cache` 本文由 tutustack 创作,采用 知识共享署名4.0 国际许可协议进行许可本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名