创见博客
强缓存和协商缓存
七崽爱吃小饼干2026/01/29阅读 1专栏 JavaScript

HTTP 强缓存与协商缓存:概念、实现及开发落地全解

一、核心概念与执行流程

HTTP 缓存的核心目标是减少不必要的网络请求、提升资源加载速度、降低服务器压力,强缓存和协商缓存是实现 HTTP 缓存的两大核心方式,其中强缓存优先级高于协商缓存,整体执行流程为:

  1. 浏览器发起资源请求,先检查本地是否有该资源的缓存副本及缓存相关响应头;
  2. 优先判断强缓存:缓存未过期则直接读取本地缓存(状态码200 OK (from memory/disk cache)),不与服务器通信;
  3. 强缓存失效则进入协商缓存:浏览器携带缓存标识向服务器发起请求,由服务器判断缓存有效性;
  4. 协商缓存结果:缓存可用返回304 Not Modified(不返回资源内容,使用本地缓存);缓存不可用返回200 OK(返回最新资源,更新本地缓存)。

1. 强缓存

浏览器通过本地缓存直接判断资源是否“新鲜”,无需与服务器通信,是性能最优的缓存方式。

  • 核心实现头:旧标准Expires(绝对GMT时间,依赖本地时间,存在篡改漏洞);新标准Cache-Control(相对秒数,不依赖本地时间,主流方案),二者共存时Cache-Control优先级更高。
  • Cache-Control常用指令:
    • max-age=xxx:核心指令,资源从接收开始的xxx秒内有效;
    • public/private:前者允许浏览器+代理服务器(CDN)缓存,后者仅允许浏览器缓存;
    • no-cache:跳过强缓存,直接进入协商缓存(非“不缓存”);
    • no-store:完全不缓存,每次请求均从服务器获取最新内容;
    • s-maxage:专用于代理服务器,优先级高于max-age。

2. 协商缓存

强缓存失效后,浏览器携带“资源标识”与服务器协商缓存有效性,需少量网络通信(仅传输标识),无大资源传输开销。 核心为两组“响应头+请求头”标识组合,二者共存时**ETag+If-None-Match优先级更高**,服务器优先对比该组标识,辅助对比Last-Modified+If-Modified-Since。

组合方式核心依据工作流程优缺点
Last-Modified+If-Modified-Since(旧标准)资源最后修改时间服务器返回Last-Modified(资源最后修改GMT时间)→ 浏览器请求携带If-Modified-Since(缓存的修改时间)→ 服务器对比时间判断是否修改优点:实现简单,服务器开销低;缺点:精度仅秒级、存在伪修改(时间变内容不变)、依赖时区时间
ETag+If-None-Match(新标准,主流)资源内容哈希值服务器返回ETag(资源内容的唯一哈希值)→ 浏览器请求携带If-None-Match(缓存的哈希值)→ 服务器重新计算哈希对比是否一致优点:精度无限制、无伪修改问题、不依赖时间;缺点:服务器需实时计算哈希,增加少量CPU负载

3. 核心区别

对比维度强缓存协商缓存
网络请求无(直接读本地缓存)有(仅传输标识,无大资源)
状态码200 OK (from memory/disk cache)304 Not Modified/200 OK
核心依赖单一响应头(Expires/Cache-Control)响应头+请求头(标识组合)
性能开销极低(无网络/服务器开销)较低(少量网络,服务器轻量计算)
灵活性低(过期时间固定)高(服务器动态判断)

二、实际开发落地实现

实际开发中缓存配置以“静态资源”为核心,动态接口按需配置,且遵循**“强缓存+协商缓存+资源指纹”组合原则**(三者缺一不可),整体落地分为前端构建预处理和服务器/CDN缓存头配置两步。

核心配置原则

  1. 静态资源(JS/CSS/图片/字体等):强缓存+协商缓存+资源指纹,兼顾性能与缓存更新有效性;
  2. HTML入口文件:禁用强缓存,仅开启协商缓存,确保能加载最新的哈希静态资源;
  3. 动态接口:实时数据(用户信息/支付结果)配置no-store(完全不缓存);非实时数据(文章列表)配置no-cache(仅协商缓存)。

步骤1:前端构建工具 - 生成资源指纹(关键前置)

资源指纹即给静态资源文件名添加内容哈希(如app.abc123.js),资源内容不变则哈希不变,浏览器持续使用强缓存;内容修改则哈希变化,浏览器认为是新资源,直接发起新请求绕过旧缓存,彻底解决“强缓存过期前资源更新无法获取”的问题。

1. Vite(主流,内置支持,配置简单)

执行npm run build自动为静态资源添加内容哈希,可在vite.config.js自定义哈希格式和资源分类:

javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  build: {
    assetsInlineLimit: 4096, // 小于4kb资源内联,减少请求
    rollupOptions: {
      output: {
        // 自定义哈希格式:8位哈希,按目录分类
        entryFileNames: 'js/[name].[hash:8].js',
        chunkFileNames: 'js/[name].[hash:8].js',
        assetFileNames: '[ext]/[name].[hash:8].[ext]'
      }
    }
  }
})

2. Webpack 5(传统项目,需插件配合)

通过output配置和MiniCssExtractPlugin插件实现,优先使用[contenthash](基于文件内容计算,优于打包批次的[hash]和代码块的[chunkhash]):

javascript
const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'js/[name].[contenthash:8].js',
    chunkFilename: 'js/[name].[contenthash:8].chunk.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true // 打包前清空dist
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash:8].css'
    })
  ],
  module: {
    rules: [
      { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] },
      {
        test: /\.(png|jpg|gif|svg|woff2)$/,
        type: 'asset/resource',
        generator: { filename: 'assets/[name].[contenthash:8].[ext]' }
      }
    ]
  }
}

步骤2:服务器/CDN - 配置缓存响应头

前端打包后,通过服务器或CDN配置缓存响应头,是实现强缓存和协商缓存的核心步骤,以下为3种主流部署方式的配置方案。

1. Nginx配置(生产环境主流,Web服务器/反向代理)

Nginx自动为静态资源开启ETag和Last-Modified,无需手动配置协商缓存,仅需通过add_header配置强缓存规则,核心配置片段:

nginx
server {
    listen 80;
    server_name localhost;
    root /usr/share/nginx/html; # 指向前端dist目录

    # 静态资源:强缓存(30天)+ 自动协商缓存
    location ~* \.(js|css|png|jpg|gif|svg|woff2|ico)$ {
        add_header Cache-Control "public, max-age=2592000"; # 30天=2592000秒
        expires 30d; # 兼容旧浏览器Expires
        etag on; # 开启ETag(默认开启)
    }

    # HTML入口:禁用强缓存,仅协商缓存
    location ~* \.html$ {
        add_header Cache-Control "public, no-cache";
        if_modified_since on; # 开启Last-Modified(默认开启)
    }

    # 动态接口:完全不缓存,转发到后端
    location /api/ {
        proxy_pass http://backend:8080;
        add_header Cache-Control "no-store";
        add_header Pragma "no-cache";
        add_header Expires "0";
    }
}

2. Node.js Express配置(Node后端项目)

通过express.static内置中间件实现,默认开启ETag和Last-Modified,可通过配置项自定义强缓存规则:

javascript
const express = require('express')
const path = require('path')
const app = express()

// 配置前端dist目录为静态资源目录
const staticPath = path.resolve(__dirname, 'dist')
app.use(express.static(staticPath, {
  maxAge: 30 * 24 * 60 * 60 * 1000, // 强缓存30天,单位毫秒
  etag: true, // 开启ETag
  lastModified: true, // 开启Last-Modified
  // 单独配置HTML:禁用强缓存
  setHeaders: (res, filePath) => {
    if (path.extname(filePath) === '.html') {
      res.setHeader('Cache-Control', 'public, no-cache')
    }
  }
}))

// 动态接口:完全不缓存
app.get('/api/user', (req, res) => {
  res.setHeader('Cache-Control', 'no-store')
  res.json({ name: '张三', age: 25 })
})

app.listen(3000, () => console.log('服务运行在http://localhost:3000'))

3. CDN配置(生产环境必备,简化服务器压力)

静态资源通常部署到CDN(阿里云/腾讯云CDN),缓存配置通过控制台可视化操作,无需修改服务器代码,核心步骤(以阿里云CDN为例):

  1. 进入CDN控制台「域名管理」,选择绑定的前端域名,进入「缓存配置」→「缓存规则配置」;
  2. 静态资源规则:匹配后缀js,css,png,jpg,缓存过期时间设为30天,缓存策略选「遵循源站」;
  3. HTML规则:匹配后缀html,缓存过期时间设为0秒(禁用强缓存);
  4. 高级配置:进入「高级缓存配置」,开启「ETag功能」和「Last-Modified功能」(默认开启);
  5. 保存配置,等待5-10分钟生效即可。

生产环境最佳实践总结

  1. 静态资源(JS/CSS/图片):前端构建加内容哈希 + 服务器/CDN配置Cache-Control: public, max-age=2592000 + 开启ETag协商缓存,实现“首次访问后30天内本地缓存,资源更新则哈希变化获取新资源,强缓存过期后协商缓存快速验证”;
  2. HTML入口文件:配置Cache-Control: public, no-cache,仅开启协商缓存,确保每次能获取最新入口,加载最新哈希静态资源;
  3. 动态接口:按实时性区分,实时数据(支付/用户信息)配no-store,非实时数据(文章/榜单)配no-cache;
  4. 部署选型:生产环境优先使用CDN配置缓存,既简化操作,又能通过就近节点提升资源加载速度,大幅减轻源站服务器压力。
评论
0/100