<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          前端緩存最佳實(shí)踐

          共 4589字,需瀏覽 10分鐘

           ·

          2021-03-19 11:40



          來源:黑金團(tuán)隊(duì)——掘金

          前言

          緩存,這是一個(gè)老生常談的話題,也常被作為前端面試的一個(gè)知識(shí)點(diǎn)。

          本文,重點(diǎn)在與探討在實(shí)際項(xiàng)目中,如何進(jìn)行緩存的設(shè)置,并給出一個(gè)較為合理的方案。

          在介紹緩存的時(shí)候,我們習(xí)慣將緩存分為強(qiáng)緩存和協(xié)商緩存兩種。兩者的主要區(qū)別是使用本地緩存的時(shí)候,是否需要向服務(wù)器驗(yàn)證本地緩存是否依舊有效。顧名思義,協(xié)商緩存,就是需要和服務(wù)器進(jìn)行協(xié)商,最終確定是否使用本地緩存。

          兩種緩存方案的問題點(diǎn)

          強(qiáng)緩存

          我們知道,強(qiáng)緩存主要是通過 http 請(qǐng)求頭中的 Cache-Control 和 Expire 兩個(gè)字段控制。Expire 是 HTTP1.0 標(biāo)準(zhǔn)下的字段,在這里我們可以忽略。我們重點(diǎn)來討論的 Cache-Control 這個(gè)字段。

          一般,我們會(huì)設(shè)置 Cache-Control 的值為 “public, max-age=xxx”,表示在xxx秒內(nèi)再次訪問該資源,均使用本地的緩存,不再向服務(wù)器發(fā)起請(qǐng)求。

          顯而易見,如果在xxx秒內(nèi),服務(wù)器上面的資源更新了,客戶端在沒有強(qiáng)制刷新的情況下,看到的內(nèi)容還是舊的。如果說你不著急,可以接受這樣的,那是不是完美?然而,很多時(shí)候不是你想的那么簡單的,如果發(fā)布新版本的時(shí)候,后臺(tái)接口也同步更新了,那就gg了。有緩存的用戶還在使用舊接口,而那個(gè)接口已經(jīng)被后臺(tái)干掉了。怎么辦?

          協(xié)商緩存

          協(xié)商緩存最大的問題就是每次都要向服務(wù)器驗(yàn)證一下緩存的有效性,似乎看起來很省事,不管那么多,你都要問一下我是否有效。但是,對(duì)于一個(gè)有追求的碼農(nóng),這是不能接受的。每次都去請(qǐng)求服務(wù)器,那要緩存還有什么意義。

          最佳實(shí)踐

          緩存的意義就在于減少請(qǐng)求,更多地使用本地的資源,給用戶更好的體驗(yàn)的同時(shí),也減輕服務(wù)器壓力。所以,最佳實(shí)踐,就應(yīng)該是盡可能命中強(qiáng)緩存,同時(shí),能在更新版本的時(shí)候讓客戶端的緩存失效。

          在更新版本之后,如何讓用戶第一時(shí)間使用最新的資源文件呢?機(jī)智的前端們想出了一個(gè)方法,在更新版本的時(shí)候,順便把靜態(tài)資源的路徑改了,這樣,就相當(dāng)于第一次訪問這些資源,就不會(huì)存在緩存的問題了。

          偉大的 webpack 可以讓我們?cè)诖虬臅r(shí)候,在文件的命名上帶上 hash 值。

          1. entry:{

          2. main: path.join(__dirname, ./main.js ),

          3. vendor: [ react , antd ]

          4. },

          5. output:{

          6. path:path.join(__dirname, ./dist ),

          7. publicPath: /dist/ ,

          8. filname: bundle.[chunkhash].js

          9. }

          綜上所述,我們可以得出一個(gè)較為合理的緩存方案:

          1. HTML:使用協(xié)商緩存。

          2. CSS&JS&圖片:使用強(qiáng)緩存,文件命名帶上hash值。

          哈希也有講究

          webpack 給我們提供了三種哈希值計(jì)算方式,分別是 hash、chunkhash 和 contenthash。那么這三者有什么區(qū)別呢?

          1. hash:跟整個(gè)項(xiàng)目的構(gòu)建相關(guān),構(gòu)建生成的文件hash值都是一樣的,只要項(xiàng)目里有文件更改,整個(gè)項(xiàng)目構(gòu)建的hash值都會(huì)更改。

          2. chunkhash:根據(jù)不同的入口文件(Entry)進(jìn)行依賴文件解析、構(gòu)建對(duì)應(yīng)的chunk,生成對(duì)應(yīng)的hash值。

          3. contenthash:由文件內(nèi)容產(chǎn)生的hash值,內(nèi)容不同產(chǎn)生的contenthash值也不一樣。

          顯然,我們是不會(huì)使用第一種的。改了一個(gè)文件,打包之后,其他文件的 hash 都變了,緩存自然都失效了。這不是我們想要的。

          那 chunkhash 和 contenthash 的主要應(yīng)用場(chǎng)景是什么呢?

          在實(shí)際在項(xiàng)目中,我們一般會(huì)把項(xiàng)目中的 css 都抽離出對(duì)應(yīng)的 css 文件來加以引用。如果我們使用 chunkhash,當(dāng)我們改了 css 代碼之后,會(huì)發(fā)現(xiàn) css 文件 hash 值改變的同時(shí),js 文件的 hash 值也會(huì)改變。這時(shí)候,contenthash 就派上用場(chǎng)了。

          ETag計(jì)算

          Nginx

          Nginx 官方默認(rèn)的 ETag 計(jì)算方式是為"文件最后修改時(shí)間16進(jìn)制-文件長度16進(jìn)制"。

          例:ETag:“59e72c84-2404”

          Express

          Express 框架使用了 serve-static 中間件來配置緩存方案,其中,使用了一個(gè)叫 etag 的 npm 包來實(shí)現(xiàn) etag 計(jì)算。從其源碼可以看出,有兩種計(jì)算方式:

          方式一:使用文件大小和修改時(shí)間

          1. function stattag (stat) {

          2. var mtime = stat.mtime.getTime().toString(16)

          3. var size = stat.size.toString(16)


          4. return " + size + - + mtime + "

          5. }

          方式二:使用文件內(nèi)容的hash值和內(nèi)容長度

          1. function entitytag (entity) {

          2. if (entity.length === 0) {

          3. // fast-path empty

          4. return "0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"

          5. }


          6. // compute hash of entity

          7. var hash = crypto

          8. .createHash( sha1 )

          9. .update(entity, utf8 )

          10. .digest( base64 )

          11. .substring(0, 27)


          12. // compute length of entity

          13. var len = typeof entity === string

          14. ? Buffer.byteLength(entity, utf8 )

          15. : entity.length


          16. return " + len.toString(16) + - + hash + "

          17. }

          ETag 與 Last-Modified 誰優(yōu)先

          協(xié)商緩存,有 ETag 和 Last-Modified 兩個(gè)字段。那當(dāng)這兩個(gè)字段同時(shí)存在的時(shí)候,會(huì)優(yōu)先以哪個(gè)為準(zhǔn)呢?

          在 Express 中,使用了 fresh 這個(gè)包來判斷是否是最新的資源。主要源碼如下:

          1. function fresh (reqHeaders, resHeaders) {

          2. // fields

          3. var modifiedSince = reqHeaders[ if-modified-since ]

          4. var noneMatch = reqHeaders[ if-none-match ]


          5. // unconditional request

          6. if (!modifiedSince && !noneMatch) {

          7. return false

          8. }


          9. // Always return stale when Cache-Control: no-cache

          10. // to support end-to-end reload requests

          11. // https://tools.ietf.org/html/rfc2616#section-14.9.4

          12. var cacheControl = reqHeaders[ cache-control ]

          13. if (cacheControl && CACHE_CONTROL_NO_CACHE_REGEXP.test(cacheControl)) {

          14. return false

          15. }


          16. // if-none-match

          17. if (noneMatch && noneMatch !== * ) {

          18. var etag = resHeaders[ etag ]


          19. if (!etag) {

          20. return false

          21. }


          22. var etagStale = true

          23. var matches = parseTokenList(noneMatch)

          24. for (var i = 0; i < matches.length; i++) {

          25. var match = matches[i]

          26. if (match === etag || match === W/ + etag || W/ + match === etag) {

          27. etagStale = false

          28. break

          29. }

          30. }


          31. if (etagStale) {

          32. return false

          33. }

          34. }


          35. // if-modified-since

          36. if (modifiedSince) {

          37. var lastModified = resHeaders[ last-modified ]

          38. var modifiedStale = !lastModified || !(parseHttpDate(lastModified) <= parseHttpDate(modifiedSince))


          39. if (modifiedStale) {

          40. return false

          41. }

          42. }


          43. return true

          44. }

          我們可以看到,如果不是強(qiáng)制刷新,而且請(qǐng)求頭帶上了 if-modified-since 和 if-none-match 兩個(gè)字段,則先判斷 etag,再判斷 last-modified。當(dāng)然,如果你不喜歡這種策略,也可以自己實(shí)現(xiàn)一個(gè)。

          后端需要怎么設(shè)置

          上文主要說的是前端如何進(jìn)行打包,那后端怎么做呢?我們知道,瀏覽器是根據(jù)響應(yīng)頭的相關(guān)字段來決定緩存的方案的。所以,后端的關(guān)鍵就在于,根據(jù)不同的請(qǐng)求返回對(duì)應(yīng)的緩存字段。以 nodejs 為例,如果需要瀏覽器強(qiáng)緩存,我們可以這樣設(shè)置:

          1. res.setHeader( Cache-Control , public, max-age=xxx );

          如果需要協(xié)商緩存,則可以這樣設(shè)置:

          1. res.setHeader( Cache-Control , public, max-age=0 );

          2. res.setHeader( Last-Modified , xxx);

          3. res.setHeader( ETag , xxx);

          總結(jié)

          在做前端緩存時(shí),我們盡可能設(shè)置長時(shí)間的強(qiáng)緩存,通過文件名加 hash 的方式來做版本更新。在代碼分包的時(shí)候,應(yīng)該將一些不常變的公共庫獨(dú)立打包出來,使其能夠更持久的緩存。

          ??愛心三連擊

          1.看到這里了就點(diǎn)個(gè)在看支持下吧,你的點(diǎn)贊,在看是我創(chuàng)作的動(dòng)力。

          2.關(guān)注公眾號(hào)程序員成長指北,回復(fù)「1」加入高級(jí)前端交流群!「在這里有好多 前端 開發(fā)者,會(huì)討論 前端 Node 知識(shí),互相學(xué)習(xí)」!

          3.也可添加微信【ikoala520】,一起成長。

          “在看轉(zhuǎn)發(fā)”是最大的支持

          瀏覽 68
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  69免费手机视频 | 精品人妻一区二区三区阅读全文 | 大香蕉伊人在线视屏 | 熟睡侵犯の奶水授乳在线 | 大香蕉97|