整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          頁面性能優化:前端緩存最佳實戰

          緩存,這是一個老生常談的話題,也常被作為前端面試的一個知識點。

          本文,重點在與探討在實際項目中,如何進行緩存的設置,并給出一個較為合理的方案。

          強緩存和協商緩存

          在介紹緩存的時候,我們習慣將緩存分為強緩存和協商緩存兩種。兩者的主要區別是使用本地緩存的時候,是否需要向服務器驗證本地緩存是否依舊有效。顧名思義,協商緩存,就是需要和服務器進行協商,最終確定是否使用本地緩存。

          兩種緩存方案的問題點

          強緩存

          我們知道,強緩存主要是通過http請求頭中的Cache-Control和Expire兩個字段控制。Expire是HTTP1.0標準下的字段,在這里我們可以忽略。我們重點來討論的Cache-Control這個字段。

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

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

          協商緩存

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

          最佳實踐

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

          在更新版本之后,如何讓用戶第一時間使用最新的資源文件呢?機智的前端們想出了一個方法,在更新版本的時候,順便把靜態資源的路徑改了,這樣,就相當于第一次訪問這些資源,就不會存在緩存的問題了。

          webpack可以讓我們在打包的時候,會在文件的命名上帶上hash值。

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

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

          • HTML:使用協商緩存。
          • CSS&JS&圖片:使用強緩存,文件命名帶上hash值。

          哈希也有講究

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

          • hash:跟整個項目的構建相關,構建生成的文件hash值都是一樣的,只要項目里有文件更改,整個項目構建的hash值都會更改。
          • chunkhash:根據不同的入口文件(Entry)進行依賴文件解析、構建對應的chunk,生成對應的hash值。
          • contenthash:由文件內容產生的hash值,內容不同產生的contenthash值也不一樣。

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

          那chunkhash和contenthash的主要應用場景是什么呢?在實際在項目中,我們一般會把項目中的css都抽離出對應的css文件來加以引用。如果我們使用chunkhash,當我們改了css代碼之后,會發現css文件hash值改變的同時,js文件的hash值也會改變。這時候,contenthash就派上用場了。

          ETag計算

          Nginx

          Nginx官方默認的ETag計算方式是為"文件最后修改時間16進制-文件長度16進制"。例:ETag: “59e72c84-2404”

          Express

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

          方式一:使用文件大小和修改時間

          function stattag (stat) {
           var mtime = stat.mtime.getTime().toString(16)
           var size = stat.size.toString(16)
           return '"' + size + '-' + mtime + '"'
          }
          

          方式二:使用文件內容的hash值和內容長度

          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誰優先

          協商緩存,有ETag和Last-Modified兩個字段。那當這兩個字段同時存在的時候,會優先以哪個為準呢?

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

          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 < matches.length; 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
          }
          

          我們可以看到,如果不是強制刷新,而且請求頭帶上了if-modified-since和if-none-match兩個字段,則先判斷etag,再判斷last-modified。當然,如果你不喜歡這種策略,也可以自己實現一個。

          補充:后端需要怎么設置

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

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

          如果需要協商緩存,則可以這樣設置:

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

          當然,現在已經有很多現成的庫可以讓我們很方便地去配置這些東西。 寫了一個簡單的demo,方便有需要的朋友去了解其中的原理,有興趣的可以閱讀源碼

          總結

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

          緩存,這是一個老生常談的話題,也常被作為前端面試的一個知識點。

          本文,重點在與探討在實際項目中,如何進行緩存的設置,并給出一個較為合理的方案。

          強緩存和協商緩存

          在介紹緩存的時候,我們習慣將緩存分為強緩存和協商緩存兩種。兩者的主要區別是使用本地緩存的時候,是否需要向服務器驗證本地緩存是否依舊有效。顧名思義,協商緩存,就是需要和服務器進行協商,最終確定是否使用本地緩存。

          兩種緩存方案的問題點

          強緩存

          我們知道,強緩存主要是通過http請求頭中的Cache-Control和Expire兩個字段控制。Expire是HTTP1.0標準下的字段,在這里我們可以忽略。我們重點來討論的Cache-Control這個字段。

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

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

          協商緩存

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

          最佳實踐

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

          在更新版本之后,如何讓用戶第一時間使用最新的資源文件呢?機智的前端們想出了一個方法,在更新版本的時候,順便把靜態資源的路徑改了,這樣,就相當于第一次訪問這些資源,就不會存在緩存的問題了。

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

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

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

          • HTML:使用協商緩存。
          • CSS&JS&圖片:使用強緩存,文件命名帶上hash值。

          哈希也有講究

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

          • hash:跟整個項目的構建相關,構建生成的文件hash值都是一樣的,只要項目里有文件更改,整個項目構建的hash值都會更改。
          • chunkhash:根據不同的入口文件(Entry)進行依賴文件解析、構建對應的chunk,生成對應的hash值。
          • contenthash:由文件內容產生的hash值,內容不同產生的contenthash值也不一樣。

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

          那chunkhash和contenthash的主要應用場景是什么呢?在實際在項目中,我們一般會把項目中的css都抽離出對應的css文件來加以引用。如果我們使用chunkhash,當我們改了css代碼之后,會發現css文件hash值改變的同時,js文件的hash值也會改變。這時候,contenthash就派上用場了。

          ETag計算

          Nginx

          Nginx官方默認的ETag計算方式是為"文件最后修改時間16進制-文件長度16進制"。例:ETag: “59e72c84-2404”

          Express

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

          • 方式一:使用文件大小和修改時間
          function stattag (stat) {
           var mtime = stat.mtime.getTime().toString(16)
           var size = stat.size.toString(16)
           return '"' + size + '-' + mtime + '"'
          }
          
          
          • 方式二:使用文件內容的hash值和內容長度
          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誰優先

          協商緩存,有ETag和Last-Modified兩個字段。那當這兩個字段同時存在的時候,會優先以哪個為準呢?

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

          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 < matches.length; 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
          }
          
          

          我們可以看到,如果不是強制刷新,而且請求頭帶上了if-modified-since和if-none-match兩個字段,則先判斷etag,再判斷last-modified。當然,如果你不喜歡這種策略,也可以自己實現一個。

          補充:后端需要怎么設置

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

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

          如果需要協商緩存,則可以這樣設置:

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

          當然,現在已經有很多現成的庫可以讓我們很方便地去配置這些東西。 寫了一個簡單的demo,方便有需要的朋友去了解其中的原理,

          總結

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

          以上,如有錯漏,歡迎指正!


          者:黑金團隊

          轉發鏈接:https://juejin.im/post/5c136bd16fb9a049d37efc47


          主站蜘蛛池模板: 免费人人潮人人爽一区二区| 亚洲一区二区三区久久| 亚洲av无码一区二区乱子伦as| 免费一本色道久久一区| 黄桃AV无码免费一区二区三区 | 国产亚洲一区二区三区在线观看 | 色妞色视频一区二区三区四区| 亚洲一区二区三区高清在线观看 | 日韩视频在线观看一区二区| 精品视频一区二区三区在线播放| 亚洲AV无码一区二区三区国产 | 久久久久人妻精品一区三寸蜜桃| 成人免费区一区二区三区| 人妻无码一区二区三区AV| 性色AV一区二区三区无码| 国产成人无码AV一区二区在线观看| 精品无码一区在线观看| 高清无码一区二区在线观看吞精| 伊人色综合网一区二区三区| 精品一区二区三区波多野结衣| 日韩AV无码一区二区三区不卡毛片| 天天看高清无码一区二区三区 | 无码人妻aⅴ一区二区三区| 日韩在线不卡免费视频一区| 亚洲乱码国产一区三区| 中文字幕一区二区视频| 国产精品 一区 在线| 一区二区三区电影网| 无码日韩人妻AV一区免费l | 极品人妻少妇一区二区三区| 无码人妻精品一区二区在线视频| 成人精品视频一区二区三区不卡| 国产成人高清精品一区二区三区 | 亚洲码欧美码一区二区三区| 亚洲丰满熟女一区二区v| 激情亚洲一区国产精品| 在线欧美精品一区二区三区| 亚洲av乱码中文一区二区三区| 亚洲熟妇av一区二区三区漫画| 国产精品美女一区二区 | 国产一区二区在线观看|