實(shí)踐示例理解js強(qiáng)緩存協(xié)商緩存
背景
無論是開發(fā)中或者是面試中,HTTP緩存都是非常重要的,這體現(xiàn)在了兩個(gè)方面:
- 開發(fā)中:合理利用HTTP緩存可以提高前端頁(yè)面的性能
- 面試中:HTTP緩存是面試中的高頻問點(diǎn)
所以本篇文章,我不講廢話,我就通過Nodejs的簡(jiǎn)單實(shí)踐,給大家講最通俗易懂的HTTP緩存,大家通過這篇文章一定能了解掌握它?。。?/p>
前置準(zhǔn)備
準(zhǔn)備
- 創(chuàng)建文件夾cache-study,并準(zhǔn)備環(huán)境
npm init
- 安裝Koa、nodemon
npm i koa -D npm i nodemon -g
- 創(chuàng)建index.js、index.html、static文件夾
- index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link rel="stylesheet" href="./static/css/index.css" rel="external nofollow" > </head> <body> <div class="box"> </div> </body> </html>
- static/css/index.css
.box { width: 500px; height: 300px; background-image: url('../image/guang.jpg'); background-size: 100% 100%; color: #000; }
- static/image/guang.jpg
- index.js
const Koa = require('koa') const fs = require('fs') const path = require('path') const mimes = { css: 'text/css', less: 'text/css', gif: 'image/gif', html: 'text/html', ico: 'image/x-icon', jpeg: 'image/jpeg', jpg: 'image/jpeg', js: 'text/javascript', json: 'application/json', pdf: 'application/pdf', png: 'image/png', svg: 'image/svg+xml', swf: 'application/x-shockwave-flash', tiff: 'image/tiff', txt: 'text/plain', wav: 'audio/x-wav', wma: 'audio/x-ms-wma', wmv: 'video/x-ms-wmv', xml: 'text/xml', } // 獲取文件的類型 function parseMime(url) { // path.extname獲取路徑中文件的后綴名 let extName = path.extname(url) extName = extName ? extName.slice(1) : 'unknown' return mimes[extName] } // 將文件轉(zhuǎn)成傳輸所需格式 const parseStatic = (dir) => { return new Promise((resolve) => { resolve(fs.readFileSync(dir), 'binary') }) } const app = new Koa() app.use(async (ctx) => { const url = ctx.request.url if (url === '/') { // 訪問根路徑返回index.html ctx.set('Content-Type', 'text/html') ctx.body = await parseStatic('./index.html') } else { const filePath = path.resolve(__dirname, `.${url}`) // 設(shè)置類型 ctx.set('Content-Type', parseMime(url)) // 設(shè)置傳輸 ctx.body = await parseStatic(filePath) } }) app.listen(9898, () => { console.log('start at port 9898') })
啟動(dòng)頁(yè)面
現(xiàn)在你可以在終端中輸入nodemon index,看到下方的顯示,則代表成功啟動(dòng)了服務(wù)
此時(shí)你可以在瀏覽器鏈接里輸入http://localhost:9898/,打開看到如下頁(yè)面,則代表頁(yè)面訪問成功!??!
HTTP緩存種類
HTTP緩存常見的有兩類:
強(qiáng)緩存:可以由這兩個(gè)字段其中一個(gè)決定
- expires
- cache-control(優(yōu)先級(jí)更高)
協(xié)商緩存:可以由這兩對(duì)字段中的一對(duì)決定
Last-Modified,If-Modified-Since
Etag,If-None-Match(優(yōu)先級(jí)更高)
強(qiáng)緩存
接下來我們就開始講強(qiáng)緩存
expires
我們只需設(shè)置響應(yīng)頭里expires的時(shí)間為當(dāng)前時(shí)間 + 30s就行了
app.use(async (ctx) => { const url = ctx.request.url if (url === '/') { // 訪問根路徑返回index.html ctx.set('Content-Type', 'text/html') ctx.body = await parseStatic('./index.html') } else { const filePath = path.resolve(__dirname, `.${url}`) // 設(shè)置類型 ctx.set('Content-Type', parseMime(url)) // 設(shè)置 Expires 響應(yīng)頭 const time = new Date(Date.now() + 30000).toUTCString() ctx.set('Expires', time) // 設(shè)置傳輸 ctx.body = await parseStatic(filePath) } })
然后我們?cè)谇岸隧?yè)面刷新,我們可以看到請(qǐng)求的資源的響應(yīng)頭里多了一個(gè)expires的字段
并且,在30s內(nèi),我們刷新之后,看到請(qǐng)求都是走memory,這意味著,通過expires設(shè)置強(qiáng)緩存的時(shí)效是30s,這30s之內(nèi),資源都會(huì)走本地緩存,而不會(huì)重新請(qǐng)求
注意點(diǎn):有時(shí)候你Nodejs代碼更新了時(shí)效時(shí)間,但是發(fā)現(xiàn)前端頁(yè)面還是在走上一次代碼的時(shí)效,這個(gè)時(shí)候,你可以把這個(gè)Disabled cache打鉤,然后刷新一下,再取消打鉤
cache-control
其實(shí)cache-control跟expires效果差不多,只不過這兩個(gè)字段設(shè)置的值不一樣而已,前者設(shè)置的是秒數(shù),后者設(shè)置的是毫秒數(shù)
app.use(async (ctx) => { const url = ctx.request.url if (url === '/') { // 訪問根路徑返回index.html ctx.set('Content-Type', 'text/html') ctx.body = await parseStatic('./index.html') } else { const filePath = path.resolve(__dirname, `.${url}`) // 設(shè)置類型 ctx.set('Content-Type', parseMime(url)) // 設(shè)置 Cache-Control 響應(yīng)頭 ctx.set('Cache-Control', 'max-age=30') // 設(shè)置傳輸 ctx.body = await parseStatic(filePath) } })
前端頁(yè)面響應(yīng)頭多了cache-control這個(gè)字段,且30s內(nèi)都走本地緩存,不會(huì)去請(qǐng)求服務(wù)端
協(xié)商緩存
與強(qiáng)緩存不同的是,強(qiáng)緩存是在時(shí)效時(shí)間內(nèi),不走服務(wù)端,只走本地緩存;而協(xié)商緩存是要走服務(wù)端的,如果請(qǐng)求某個(gè)資源,去請(qǐng)求服務(wù)端時(shí),發(fā)現(xiàn)命中緩存則返回304,否則則返回所請(qǐng)求的資源,那怎么才算命中緩存呢?接下來講講
Last-Modified,If-Modified-Since
簡(jiǎn)單來說就是:
- 第一次請(qǐng)求資源時(shí),服務(wù)端會(huì)把所請(qǐng)求的資源的最后一次修改時(shí)間當(dāng)成響應(yīng)頭中Last-Modified的值發(fā)到瀏覽器并在瀏覽器存起來
- 第二次請(qǐng)求資源時(shí),瀏覽器會(huì)把剛剛存儲(chǔ)的時(shí)間當(dāng)成請(qǐng)求頭中If-Modified-Since的值,傳到服務(wù)端,服務(wù)端拿到這個(gè)時(shí)間跟所請(qǐng)求的資源的最后修改時(shí)間進(jìn)行比對(duì)
- 比對(duì)結(jié)果如果兩個(gè)時(shí)間相同,則說明此資源沒修改過,那就是命中緩存,那就返回304,如果不相同,則說明此資源修改過了,則沒命中緩存,則返回修改過后的新資源
// 獲取文件信息 const getFileStat = (path) => { return new Promise((resolve) => { fs.stat(path, (_, stat) => { resolve(stat) }) }) } app.use(async (ctx) => { const url = ctx.request.url if (url === '/') { // 訪問根路徑返回index.html ctx.set('Content-Type', 'text/html') ctx.body = await parseStatic('./index.html') } else { const filePath = path.resolve(__dirname, `.${url}`) const ifModifiedSince = ctx.request.header['if-modified-since'] const fileStat = await getFileStat(filePath) console.log(new Date(fileStat.mtime).getTime()) ctx.set('Cache-Control', 'no-cache') ctx.set('Content-Type', parseMime(url)) // 比對(duì)時(shí)間,mtime為文件最后修改時(shí)間 if (ifModifiedSince === fileStat.mtime.toGMTString()) { ctx.status = 304 } else { ctx.set('Last-Modified', fileStat.mtime.toGMTString()) ctx.body = await parseStatic(filePath) } } })
第一次請(qǐng)求時(shí),響應(yīng)頭中:
第二次請(qǐng)求時(shí),請(qǐng)求頭中:
由于資源并沒修改,則命中緩存,返回304:
此時(shí)我們修改一下index.css
.box { width: 500px; height: 300px; background-image: url('../image/guang.jpg'); background-size: 100% 100%; /* 修改這里 */ color: #333; }
然后我們刷新一下頁(yè)面,index.css變了,所以會(huì)沒命中緩存,返回200和新資源,而guang.jpg并沒有修改,則命中緩存返回304:
Etag,If-None-Match
其實(shí)Etag,If-None-Match跟Last-Modified,If-Modified-Since大體一樣,區(qū)別在于:
- 后者是對(duì)比資源最后一次修改時(shí)間,來確定資源是否修改了
- 前者是對(duì)比資源內(nèi)容,來確定資源是否修改
那我們要怎么比對(duì)資源內(nèi)容呢?我們只需要讀取資源內(nèi)容,轉(zhuǎn)成hash值,前后進(jìn)行比對(duì)就行了?。?/p>
const crypto = require('crypto') app.use(async (ctx) => { const url = ctx.request.url if (url === '/') { // 訪問根路徑返回index.html ctx.set('Content-Type', 'text/html') ctx.body = await parseStatic('./index.html') } else { const filePath = path.resolve(__dirname, `.${url}`) const fileBuffer = await parseStatic(filePath) const ifNoneMatch = ctx.request.header['if-none-match'] // 生產(chǎn)內(nèi)容hash值 const hash = crypto.createHash('md5') hash.update(fileBuffer) const etag = `"${hash.digest('hex')}"` ctx.set('Cache-Control', 'no-cache') ctx.set('Content-Type', parseMime(url)) // 對(duì)比hash值 if (ifNoneMatch === etag) { ctx.status = 304 } else { ctx.set('etag', etag) ctx.body = fileBuffer } } })
驗(yàn)證方式跟剛剛Last-Modified,If-Modified-Since的一樣,這里就不重復(fù)說明了。。。
總結(jié)
參考 http://www.dbjr.com.cn/article/254078.htm
以上就是實(shí)踐示例理解js強(qiáng)緩存協(xié)商緩存的詳細(xì)內(nèi)容,更多關(guān)于js強(qiáng)緩存協(xié)商緩存的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
TypeScript實(shí)用技巧?Nominal?Typing名義類型詳解
這篇文章主要為大家介紹了TypeScript實(shí)用技巧?Nominal?Typing名義類型詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09微信小程序 (一)新建項(xiàng)目hello WeApp 詳細(xì)介紹
這篇文章主要介紹了微信小程序 (一)hello WeApp 詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下2016-09-09ECMAScript?6數(shù)組的擴(kuò)展實(shí)例詳解
這篇文章主要為大家介紹了ECMAScript?6數(shù)組的擴(kuò)展實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08Promise靜態(tài)四兄弟實(shí)現(xiàn)示例詳解
這篇文章主要為大家介紹了Promise靜態(tài)四兄弟實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07微信小程序中頁(yè)面FOR循環(huán)和嵌套循環(huán)
這篇文章主要介紹了微信小程序中頁(yè)面FOR循環(huán)和嵌套循環(huán)的相關(guān)資料,需要的朋友可以參考下2017-06-06js面向?qū)ο缶幊蘋OP及函數(shù)式編程FP區(qū)別
這篇文章主要為大家介紹了js面向?qū)ο缶幊蘋OP及函數(shù)式編程FP的區(qū)別詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07JavaScript復(fù)原何同學(xué)B站頭圖細(xì)節(jié)示例詳解
這篇文章主要為大家介紹了JavaScript復(fù)原何同學(xué)B站頭圖細(xì)節(jié)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07