1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        前端緩存最佳實(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 請求頭中的 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ā)起請求。

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

        協(xié)商緩存

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

        最佳實(shí)踐

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

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

        偉大的 webpack 可以讓我們在打包的時(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)建對應(yīng)的chunk,生成對應(yīng)的hash值。

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

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

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

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

        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)制刷新,而且請求頭帶上了 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ù)不同的請求返回對應(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)注公眾號程序員成長指北,回復(fù)「1」加入高級前端交流群!「在這里有好多 前端 開發(fā)者,會(huì)討論 前端 Node 知識(shí),互相學(xué)習(xí)」!

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

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

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

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            干B网 | 日本公妇乱淫 | 欧美成人精品A片免费一区99 | 黄a一级片 | 李采潭三点尽露三级 | 日韩精品一级毛片免费视频 | bytv跳转接口点击进入网页 | 国产激情视频一区 | 日韩精品一区二区三区免费视频 | 九一果冻厂最新电视剧潘甜甜 |