前言
在前端開(kāi)發(fā)中,網(wǎng)站性能是非常重要的一點(diǎn),一個(gè)優(yōu)秀的緩存策略能夠減少帶寬,降低網(wǎng)絡(luò)負(fù)荷,減少延遲。本文簡(jiǎn)單聊一下瀏覽器緩存。
緩存分類
Web緩存分為多種, 如數(shù)據(jù)庫(kù)緩存、DNS緩存、CDN緩存,以及瀏覽器緩存等。
瀏覽器緩存
一、什么是瀏覽器緩存?

簡(jiǎn)單一點(diǎn)說(shuō),瀏覽器緩存就是將通過(guò)HTTP請(qǐng)求獲取的資源保存在本地的一種行為。
二、緩存位置
- memory cache
- disk cache
三、三級(jí)緩存原理 (訪問(wèn)緩存優(yōu)先級(jí))
- 先在內(nèi)存中查找,如果有,直接加載。
- 如果內(nèi)存中不存在,則在硬盤中查找,如果有直接加載。
- 如果硬盤中也沒(méi)有,那么就進(jìn)行網(wǎng)絡(luò)請(qǐng)求。
- 請(qǐng)求獲取的資源緩存到硬盤和內(nèi)存。
四、瀏覽器緩存的分類
強(qiáng)緩存
瀏覽器在加載資源時(shí),會(huì)先根據(jù)本地緩存資源的 header 中的信息判斷是否命中強(qiáng)緩存,如果命中則直接使用緩存中的資源不會(huì)再向服務(wù)器發(fā)送請(qǐng)求。在chrome控制臺(tái)的Network選項(xiàng)中可以看到該請(qǐng)求返回200的狀態(tài)碼,并且Size顯示from disk cache或from memory cache。強(qiáng)緩存可以通過(guò)設(shè)置兩種 HTTP Header 實(shí)現(xiàn):Expires 和 Cache-Control。協(xié)商緩存
當(dāng)強(qiáng)緩存沒(méi)有命中的時(shí)候,瀏覽器會(huì)發(fā)送一個(gè)請(qǐng)求到服務(wù)器,服務(wù)器根據(jù) header 中的部分信息來(lái)判斷是否命中緩存。如果命中,則返回 304 ,告訴瀏覽器資源未更新,可使用本地的緩存。這里的 header 中的信息指的是 Last-Modify/If-Modify-Since 和 ETag/If-None-Match。
緩存機(jī)制
強(qiáng)制緩存優(yōu)先于協(xié)商緩存進(jìn)行,若強(qiáng)制緩存(Expires和Cache-Control)生效則直接使用緩存,若不生效則進(jìn)行協(xié)商緩存(Last-Modified / If-Modified-Since和Etag / If-None-Match),協(xié)商緩存由服務(wù)器決定是否使用緩存,若協(xié)商緩存失效,那么代表該請(qǐng)求的緩存失效,返回200,重新返回資源和緩存標(biāo)識(shí),再存入瀏覽器緩存中;生效則返回304,繼續(xù)使用緩存。具體流程如下。

實(shí)踐
一、搭建服務(wù)
這里用Egg.js 框架起一個(gè)WEB服務(wù)器(是阿里開(kāi)源的基于Koa2的企業(yè)級(jí) Node.js 框架)。
對(duì)強(qiáng)緩存的Cache-Control和協(xié)商緩存的Etag 進(jìn)行測(cè)試。
- 項(xiàng)目初始化
$ npm i egg-init -g
$ egg-init egg-web --type=simple
$ cd egg-web
$ npm i
- 編寫(xiě) Controller
// app/controller/cache.js
const Controller = require('egg').Controller
class CacheController extends Controller {
async cache() {
await this.ctx.render('cache/index.tpl')
}
}
module.exports = CacheController
- 配置路由映射
//app/router.js
module.exports = app => {
const {router, controller} = app
router.get('/cache', controller.cache.cache)
}
- 靜態(tài)資源
Egg 內(nèi)置了 static 插件,線上環(huán)境建議部署到 CDN,無(wú)需該插件。static 插件默認(rèn)映射 /public/* -> app/public/* 目錄
此處,我們把靜態(tài)資源都放到 app/public 目錄即可:
app/public
├── css
│ └── cache.css
└── img
├── flow.png
└── ppd.png
- 模板渲染
// app/view/cache/index.tpl
<html>
<head>
<title>前端緩存</title>
<link rel="stylesheet" href="/public/css/cache.css" />
<meta name="viewport" content="width=device-width, initial-scale=1,
maximum-scale=1, minimum-scale=1, user-scalable=no">
</head>
<body>
<div class = "content">
<h3>Front-end cache testing</h3>
<img src="/public/img/ppd.png"/>
</div>
</body>
</html>
6.啟動(dòng)服務(wù)
$ npm run dev
輸入地址
http://127.0.0.1:7001/cache

二、強(qiáng)緩存和協(xié)商緩存實(shí)踐
主要演示下cacheControl與ETag設(shè)置的效果
// config/config.default.js
const path = require('path');
module.exports = (appInfo) => {
const config = exports = {};
...
config.static = {
prefix: '/public/',
dir: path.join(appInfo.baseDir, 'app/public'),
cacheControl: 'max-age=600'
}
....
return config;
};

此時(shí),瀏覽器把ppd.png圖片緩存到了磁盤和內(nèi)存中,刷新頁(yè)面后會(huì)先在內(nèi)存中找到資源,如下圖所示:

關(guān)閉Tab頁(yè)面,由于內(nèi)存是存在進(jìn)程中的,關(guān)閉頁(yè)面,內(nèi)存中的資源被釋放掉,磁盤中的資源是永久性的,所以還存在。根據(jù)三級(jí)緩存原理,重新打開(kāi)頁(yè)面,便會(huì)在磁盤中找到資源。

剛剛對(duì)資源的有效期設(shè)置的是600秒,600秒后重新刷新頁(yè)面,可以清楚的看到,強(qiáng)緩存失效了,并命中了協(xié)商緩存。如下圖所示。
egg-static靜態(tài)服插件,基礎(chǔ)與koa的靜態(tài)插件 koa-static-cache.Uses MD5 hash sum as an ETag.

驗(yàn)證服務(wù)器端資源變化時(shí),協(xié)商緩存是否失效,修改ppd.png,刷新頁(yè)面,此時(shí),消息頭結(jié)果如下:
Gerneral
Status Code: 200 OK
Response Headers
ETag:"vSoLmwZqMEbQmnfRGN3p/w=="
Request Headers
If-None-Match:"+xVGjRSF2PfxkkUeQPrJyQ=="
可以看出, If-None-Match !== ETag,協(xié)商緩存失效,瀏覽器重新向服務(wù)器發(fā)起請(qǐng)求。
結(jié)語(yǔ)
本文簡(jiǎn)單介紹了下瀏覽器的緩存,希望對(duì)大家有點(diǎn)小的幫助,限于篇幅,每一部分的描述都比較簡(jiǎn)略,僅起到拋磚引玉之用。如有錯(cuò)誤,還望指出。