浏览器原理 · 27/51
1. addEventListener 第三个参数 2. addEventListener 与 attachEvent 区别 3. 浏览器兼容性测试与内核 4. 浏览器兼容性问题 5. 浏览器内核与引擎 6. 浏览器图层创建条件 7. 浏览器多进程架构 8. 浏览器渲染机制 9. 浏览器存储方案 10. 浏览器版本检测方法 11. children 与 childNodes 区别 12. 常见浏览器兼容性问题 13. Chrome 页面进程数量 14. 坐标系统对比 15. 多标签页通讯方案 16. 删除 Cookie 17. 自定义事件 18. DOM 事件处理方式演进 19. 元素尺寸属性对比 20. DOM 节点操作 21. DOM 事件机制 22. addEventListener 与 attachEvent 的区别 23. 获取页面所有复选框 24. HTMLCollection 与 NodeList 区别 25. Hybrid 应用开发 26. 强缓存命中机制 27. 浏览器缓存机制 28. 页面编码与资源编码不一致处理 29. jQuery 事件绑定方法对比 30. Input 点击触发的事件顺序 31. JavaScript 浏览器兼容性问题 32. jQuery 多事件绑定实现 33. JSBridge 原理 34. 链接点击后 Hover 失效解决方案 35. 减少重绘和回流的性能优化 36. 移动端 300ms 点击延迟问题 37. 移动端视口配置 38. 移动端点击穿透问题解决 39. 移动端兼容性问题 40. JSBridge 原理与实现 41. 移动端 1px 像素问题解决方案 42. 浏览器渲染流程 43. 页面加载完成事件对比 44. Offset、Scroll、Client 属性对比 45. 同源策略与跨域解决方案 46. Script 标签位置对页面加载的影响 47. Service Worker 与 PWA 48. 存储方案对比:Cookie、Storage、IndexedDB 49. 强缓存默认时间 50. URL 到页面显示的完整过程 51. V8 引擎 JavaScript 执行过程

浏览器缓存机制

强缓存与协商缓存的工作原理和使用场景

问题

浏览器缓存分为强缓存和协商缓存,它们分别是如何工作的?Cache-Control、Expires、ETag、Last-Modified 各有什么作用?

解答

缓存流程

请求资源

检查强缓存(Cache-Control/Expires)

命中 → 直接使用缓存(200 from cache)
未命中 ↓
发送请求,携带协商缓存标识(If-None-Match/If-Modified-Since)

服务器验证

未修改 → 304 Not Modified,使用缓存
已修改 → 200 OK,返回新资源

强缓存

不向服务器发送请求,直接从缓存读取资源。

# Expires(HTTP/1.0)- 绝对时间,受客户端时间影响
Expires: Wed, 21 Oct 2025 07:28:00 GMT

# Cache-Control(HTTP/1.1)- 相对时间,优先级更高
Cache-Control: max-age=31536000

Cache-Control 常用指令:

# 缓存 1 年
Cache-Control: max-age=31536000

# 禁止缓存
Cache-Control: no-store

# 每次都要验证(走协商缓存)
Cache-Control: no-cache

# 只能被浏览器缓存,不能被 CDN 缓存
Cache-Control: private

# 可以被 CDN 等中间代理缓存
Cache-Control: public

协商缓存

向服务器发送请求,由服务器判断是否使用缓存。

Last-Modified / If-Modified-Since:

# 首次响应,服务器返回资源最后修改时间
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT

# 再次请求,浏览器携带该时间
If-Modified-Since: Wed, 21 Oct 2024 07:28:00 GMT

ETag / If-None-Match:

# 首次响应,服务器返回资源唯一标识
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"

# 再次请求,浏览器携带该标识
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"

Node.js 实现示例

const http = require('http');
const fs = require('fs');
const crypto = require('crypto');

http.createServer((req, res) => {
  const filePath = './static' + req.url;
  const stat = fs.statSync(filePath);
  const content = fs.readFileSync(filePath);
  
  // 生成 ETag
  const etag = crypto.createHash('md5').update(content).digest('hex');
  
  // 获取最后修改时间
  const lastModified = stat.mtime.toUTCString();
  
  // 检查协商缓存
  if (req.headers['if-none-match'] === etag ||
      req.headers['if-modified-since'] === lastModified) {
    res.writeHead(304);
    res.end();
    return;
  }
  
  // 设置缓存头
  res.setHeader('Cache-Control', 'max-age=3600'); // 强缓存 1 小时
  res.setHeader('ETag', etag);
  res.setHeader('Last-Modified', lastModified);
  
  res.end(content);
}).listen(3000);

实际应用策略

# Nginx 配置示例

# HTML - 不缓存或短时间缓存
location ~* \.html$ {
    add_header Cache-Control "no-cache";
}

# 带 hash 的静态资源 - 长期缓存
location ~* \.(js|css|png|jpg|gif|ico)$ {
    add_header Cache-Control "max-age=31536000, immutable";
}

# API 接口 - 不缓存
location /api/ {
    add_header Cache-Control "no-store";
}

关键点

  • 强缓存优先级:Cache-Control > Expires,前者用相对时间更可靠
  • 协商缓存优先级:ETag > Last-Modified,前者基于内容 hash 更精确
  • Last-Modified 缺陷:精度只到秒级,文件内容不变但修改时间变了也会失效
  • 缓存策略:HTML 用 no-cache 保证更新,带 hash 的静态资源用长期缓存
  • 304 vs 200:304 只返回响应头,节省带宽;200 from cache 完全不发请求