<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í)踐

          共 3335字,需瀏覽 7分鐘

           ·

          2020-04-02 23:23


          來自:掘金,作者:?黑金團(tuán)隊(duì)

          https://juejin.im/post/5c136bd16fb9a049d37efc47

          前言

          緩存,這是一個(gè)老生常談的話題,也常被作為前端面試的一個(gè)知識點(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è)字段。

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

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

          協(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)于第一次訪問這些資源,就不會存在緩存的問題了。

          偉大的 webpack 可以讓我們在打包的時(shí)候,在文件的命名上帶上 hash 值。

          entry:{
          ????main:?path.join(__dirname,'./main.js'),
          ????vendor:?['react',?'antd']
          },
          output:{
          ????path:path.join(__dirname,'./dist'),
          ????publicPath:?'/dist/',
          ????filname:?'bundle.[chunkhash].js'
          }

          綜上所述,我們可以得出一個(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值都會更改。

          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值也不一樣。

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

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

          在實(shí)際在項(xiàng)目中,我們一般會把項(xiàng)目中的 css 都抽離出對應(yīng)的 css 文件來加以引用。如果我們使用 chunkhash,當(dāng)我們改了 css 代碼之后,會發(fā)現(xiàn) css 文件 hash 值改變的同時(shí),js 文件的 hash 值也會改變。這時(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í)間

          function?stattag?(stat)?{
          ??var?mtime?=?stat.mtime.getTime().toString(16)
          ??var?size?=?stat.size.toString(16)

          ??return?'"'?+?size?+?'-'?+?mtime?+?'"'
          }

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

          function?entitytag?(entity)?{
          ??if?(entity.length?===?0)?{
          ????//?fast-path?empty
          ????return?'"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"'
          ??}

          ??//?compute?hash?of?entity
          ??var?hash?=?crypto
          ????.createHash('sha1')
          ????.update(entity,?'utf8')
          ????.digest('base64')
          ????.substring(0,?27)

          ??//?compute?length?of?entity
          ??var?len?=?typeof?entity?===?'string'
          ??????Buffer.byteLength(entity,?'utf8')
          ????:?entity.length

          ??return?'"'?+?len.toString(16)?+?'-'?+?hash?+?'"'
          }

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

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

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

          function?fresh?(reqHeaders,?resHeaders)?{
          ??//?fields
          ??var?modifiedSince?=?reqHeaders['if-modified-since']
          ??var?noneMatch?=?reqHeaders['if-none-match']

          ??//?unconditional?request
          ??if?(!modifiedSince?&&?!noneMatch)?{
          ????return?false
          ??}

          ??//?Always?return?stale?when?Cache-Control:?no-cache
          ??//?to?support?end-to-end?reload?requests
          ??//?https://tools.ietf.org/html/rfc2616#section-14.9.4
          ??var?cacheControl?=?reqHeaders['cache-control']
          ??if?(cacheControl?&&?CACHE_CONTROL_NO_CACHE_REGEXP.test(cacheControl))?{
          ????return?false
          ??}

          ??//?if-none-match
          ??if?(noneMatch?&&?noneMatch?!==?'*')?{
          ????var?etag?=?resHeaders['etag']

          ????if?(!etag)?{
          ??????return?false
          ????}

          ????var?etagStale?=?true
          ????var?matches?=?parseTokenList(noneMatch)
          ????for?(var?i?=?0;?i???????var?match?=?matches[i]
          ??????if?(match?===?etag?||?match?===?'W/'?+?etag?||?'W/'?+?match?===?etag)?{
          ????????etagStale?=?false
          ????????break
          ??????}
          ????}

          ????if?(etagStale)?{
          ??????return?false
          ????}
          ??}

          ??//?if-modified-since
          ??if?(modifiedSince)?{
          ????var?lastModified?=?resHeaders['last-modified']
          ????var?modifiedStale?=?!lastModified?||?!(parseHttpDate(lastModified)?<=?parseHttpDate(modifiedSince))

          ????if?(modifiedStale)?{
          ??????return?false
          ????}
          ??}

          ??return?true
          }

          我們可以看到,如果不是強(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è)置:

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


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


          res.setHeader('Cache-Control',?'public,?max-age=0');
          res.setHeader('
          Last-Modified',?xxx);
          res.setHeader('
          ETag',?xxx);

          總結(jié)

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

          分享前端好文,點(diǎn)亮?在看?aeb5f1789b532ed1167485d3c1148302.webp

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

          手機(jī)掃一掃分享

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

          手機(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>
                  大鸡吧视频在线免费观看 | av中文字幕第一页 | 午夜人妻无码 | 99热精品中文在线播放 | 国产婷婷色一区二区三区 |