📖
blog
  • README
  • JavaScript
    • 元素的宽高位置信息梳理
    • dom-align 源码浅析
    • Event Loop
    • 函数实参为对象时的陷阱
    • export 与 utils 方法书写规范
    • 手写 Promise 及相关代码理解
    • call,apply,bind 等函数的模拟实现
    • JavaScript继承
    • JavaScript 数据类型与类型判断
    • for..of 和 for..in 的区别
    • 写给自己看的 next 函数
    • JS 可选链与双问号
    • mouseenter 与 mouseover 事件的区别
    • Immutable相关知识
  • CSS
    • 不简单的 z-index
    • 两列布局,三列布局
    • CSS 居中方案整理
    • CSS 像素,设备像素,2倍图梳理
    • iconfont 的使用
  • Node JS
    • 实现简易的 express
  • React 核心知识点整理
    • 高阶组件
    • React 事件处理
    • React Hooks
    • React Context
  • React 状态管理
    • Redux 基础概念
    • Redux 中间件和异步操作
    • Redux Saga
    • Redux 只能有一个 store 对象嘛
  • React 开发实践
    • Ant Design Menu 组件的使用与深入
    • 讲讲吸顶效果与 react sticky
    • 基于 express,搭建 react 的开发环境
    • 通过 antd input 组件分析受控与非受控组件
    • DebounceClick 组件
    • react component Align 组件分析
    • React Portal 之事件冒泡
    • React Transition Group 源码浅析
    • React.cloneElement 父组件向子组件注入 props
    • 一次 Align 组件的问题记录
    • 如何知道子组件的类型
    • React Router 源码简单分析
    • React Redux 源码简单分析
  • Vue.js
    • Vue.js 概览
    • scoped 样式中的 deep
  • TypeScript 语法
    • 基础类型
    • 变量声明
    • 接口
    • 类
    • 函数
    • 泛型
    • 枚举
    • 类型推论
    • 类型兼容性
    • 高级类型
    • Symbol
    • 迭代器和生成器
    • 模块
    • 命名空间
    • JSX
  • 玩转 webpack
    • 第一章: webpack 与构建发展简史
    • 第二章:webpack基础用法
    • 第三章:webpack进阶用法
    • 第四章:编写可维护的 webpack 构建配置
    • 第五章:webpack构建速度和体积优化策略
    • 第六章:通过源代码掌握webpack打包原理
    • 第七章:编写Loader和插件
  • webpack 实践
    • 如何配置 output.library
  • 测试
    • 初识代码测试
    • Jest 中 如何测试 setTimeout
    • Jest Enzyme React 测试实践记录
  • WEB 开发,过往工作沉淀
    • Web安全(DVWA)
    • 内存泄露与事件移除的必要性
    • url to pdf api 与 服务部署踩坑记录
    • 前端调试指南
    • Markdown 转 email
    • github travis ci 自动部署
    • 浏览器缓存知识梳理
    • WEB 系统登录相关知识梳理
    • 将-Axios-请求参数和返回值进行格式化
    • source-map与源码调试
    • HTTPS
    • 使用 rollup 打造自己的 npm 包 (全流程)
    • father-build 是如何工作的
  • 书籍
    • 图解 HTTP 协议
    • 编写可维护的 JavaScript
    • 鸟哥的 Linux 私房菜
    • JavaScript Promise迷你书
  • Linux
    • vimtutor
    • CURL 使用指南
  • Nginx
    • 一次 nginx 分享
  • Git
    • Git Commit Message 须知
    • .gitignore 模板
    • git tag标签
  • 摄影
    • 摄影基础知识
    • 手机摄影从小白到大师
  • 翻译
    • log4js
    • log4js-node
    • 介绍GitLab上的CI/CD
    • 为GitLab Pages创建并调整GitLab CI/CD
    • 关于 rel=noopener
    • AngularJS 团队 Git 提交信息约定
    • JSON Schema
  • Lifehack
    • 20 个 Google 搜索 Tips 来高效使用 Google
    • 37 个高级 Google 搜索 Tips
Powered by GitBook
On this page
  • 前言
  • 1. 缓存
  • 1.1 缓存位置
  • 1.2 缓存策略
  • 1.3 Service Worker 介绍
  • 1.4 Memory Cache 介绍
  • 1.5 命中强缓存时, 该从哪拿缓存
  • 2. Cache-Control 常用指令
  • 3. 协商缓存
  • 3.1 协商缓存的两种方式
  • 4. 缓存机制
  • 5. 实际场景策略
  • 参考资料

Was this helpful?

  1. WEB 开发,过往工作沉淀

浏览器缓存知识梳理

前言

在上家公司时,有同事分享过关于缓存的知识,听完便意识到这一部分的内容是我不熟悉的,一直想着要把这块知识明白。

一方面是因为公司 Ares 静态服务偶尔资源非立即更新的机制(可能是因为缓存?),另外一方面在 webpack 打包文件时,也会选择打包出带有 hash 的资源。而这些知识,隐隐中也觉得和缓存知识相关。

但自己总是不挤时间出来弄明白这块知识, 在晃晃中继续开发~~

如今,受面试问题所扰,以及离职了时间空下来了,那就来整理整理知识。

1. 缓存

1.1 缓存位置

四种缓存位置,优先级从下往上(即如果有 Service Worker, 先查询 Service Worker,否则查询 Memory Worker)

  • Service Worker (即使关闭浏览器,也不会消失。具体待了解)

  • Memory Cache

  • Disk Cache

  • Push Cache

1.2 缓存策略

强缓存: Expires, Cache-Control (命中强缓存返回状态码: 200)

协商缓存: Last-Modified / If-Modified-Since, 或 Etag 与 If-None-Match

以上这些字段控制的缓存都是 Disk Cache

1.3 Service Worker 介绍

Service Worker 的缓存与浏览器其他内建缓存机制不同,它可以让我们自由控制缓存哪些文件,如何匹配缓存,如何读取缓存,并且缓存时可持续的。

1.4 Memory Cache 介绍

即内存中的缓存,读写比 Disk Cache 高效,但是容量更小。

  1. 一旦关闭标签页,内存中的缓存也就释放了,经过测试:

    1. 第一次打开 www.baidu.com,大部分资源从服务器中请求

    2. 刷新页面,大部分资源从 Memory Cache 或者 Disk Cache 中获取

    3. 关闭该标签页,打开 www.baidu.com, 大部分资源从 Disk Cache 中获取。

就像是,在关闭标签页或者浏览器的时候,会将 Memory Cache 中的东西,放到 Disk Cache 中

  1. Cache-Control 与 Memory Cache 的关系, 经过测试: 1. 不设置 Cache-Control, 再次刷新页面不会出现 Memory Cache 2. 设置 Cache-Control:max-age=0; 再次刷新页面不会出现 Memory Cache 3. 设置 Cache-Control:max-age=60; 之后每次刷新页面都是出现 Memory Cache,一直到 60s 后,下一次刷新又是从服务器从获取。

  2. Expires 与 Memory Cache 的关系,经过测试: 1. 不设置 Expires, 再次刷新页面不会出现 Memory Cache 2. 设置 Expires:(new Date()).toUTCString(); 即设置为当前时间,再次刷新页面不会出现 Memory Cache 3. 设置 Expires:(new Date() + * ).toUTCString(); 即设置为当前时间+一定时间,再次刷新页面会出现 Memory Cache

由以上大概知道,Expires 和 Cache Control 对于强制缓存有相同效果,即如果设置为强缓存一段时间,那么就会从 Memory Cache 中获取资源。

  1. Cache-Control 优先级大于 Expires

    1. 设置 Expires:(new Date() + * ).toUTCString(); 同时设置 Cache-Control:max-age=0; 刷新页面不会出现 Memory Cache, 即 Cache-Control 覆盖了 Expires

    2. 设置 Expires:(new Date() + * ).toUTCString(); 同时设置 Cache-Control:max-age=60; 刷新页面会出现 Memory Cache

1.5 命中强缓存时, 该从哪拿缓存

  1. 如果开启了 Service Worker, 首先从 Service Worker 中拿

  2. 如果打开(打开标签页,或者重开浏览器)一个新页面(该页面的资源以前被缓存过),那么就会从 Disk Cache 中拿

  3. 刷新当前页面时,浏览器可能从 Disk Cache, 也可能从 Memory Cache 中获取

2. Cache-Control 常用指令

指令

作用

Cache-Control: public;

表示响应可以被客户端,和中间代理服务器缓存

Cache-Control:private;

响应结果只允许客户端缓存,不允许中间代理服务器缓存

Cache-Control:max-age=30;

强制缓存30s,之后需要重新请求

Cache-Control:no-store;

所有内容都不会被缓存,强缓存和协商缓存都失效

Cache-Control: no-cache;

跳过强缓存,根据协商缓存判断是否使用缓存数据

3. 协商缓存

即当强缓存失效(过期)之后,浏览器带有缓存标识向服务器发起请求,由服务器根据缓存标识判断是否使用缓存的过程,主要有以下两种情况:

  1. 强制缓存过期,协商缓存生效 1. 浏览器发起请求 A 2. 浏览器向浏览器缓存发起查询,查询 A 是否有缓存,发现有缓存,但是缓存失效了,因此需要进行协商缓存。 3. 携带 A 资源的缓存标识,去请求服务端 4. 服务端根据 A 资源的缓存标识,认为 A 资源没有改变。于是服务端返回 304 Not Modified. 5. 浏览器得到 304 之后,就可以使用浏览器缓存中的缓存资源了

  2. 强制缓存过期,协商缓存过期 1. 浏览器发起请求 A 2. 浏览器向浏览器缓存发起查询,查询 A 是否有缓存,发现有缓存,但是缓存失效了,因此需要进行协商缓存。 3. 携带 A 资源的缓存标识,去请求服务端 4. 服务端根据 A 资源的缓存标识,认为 A 资源已经发生改变了。于是服务端返回 200 OK, 并返回最新的 A 资源. 5. 浏览器得到 200 之后,得到最新的 A 资源,同时使用 A 资源更新浏览器缓存

3.1 协商缓存的两种方式

方式1, Last-Modified 和 If-Modified-Since

  1. 浏览器第一次请求 A 资源的时候,除了返回 A 资源,同时在响应头(response header) 中添加 Last-Modified 的头,这个值表示 A 文件在服务器上的最后修改时间。同时将 A 资源缓存到浏览器缓存中。

Last-Modified: Fri, 22 Jul 2016 01:47:00 GMT
  1. 浏览器再次请求 A 资源的时候,检测到 A 资源有 Last-Modified 这个响应头,于是浏览器给 A 资源请求添加 If-Modified-Since 这个请求头,而值就是 Last-Modified 的值,再次请求服务器

  2. 服务器收到这个请求的时候,根据 If-Modified-Since 的值,与服务器中该资源最后修改时间做对比。

  3. 如果时间没有变化,那么就返回 304 状态码和空的响应体。那浏览器根据 304 状态码,会直接去浏览器缓存中获取 A 资源。

  4. 而如果 If-Modified-Since 的时间小于 (大于服务器中最后修改时间[说明客户端的If-Modified-Since 被主动修改过],也应该返回最新的资源 ) 服务器中的最后修改时间,说明文件有更新,于是返回新资源 A 和 200 状态码,并更新浏览器缓存。

缺点: 1. 如果本地打开缓存文件,即使没有修改文件,也会造成 Last-Modified 的修改,服务端不能命中缓存(即认为文件发生了变化,需要发送最新的资源),导致发送相同的资源。 2. 因为 Last-Modified 只能精确到秒,如果先返回 A,在 1s 内又修改了 A 的内容,那么会认为 A 文件没有修改,返回错误的文件。

由于以上的缺点,即根据文件修改时间来决定缓存会有问题,于是有了 Etag 和 If-None-Match 的方式: 根据文件内容是否修改来决定缓存策略。

方式2,Etag 和 If-None-Match

Etag 是服务器响应请求时,返回该资源的唯一标识(由服务器生成),只要资源有变化,Etag 就会重新生成。

  1. 浏览器第一次请求 A 资源的时候,除了返回 A 资源,同时在响应头(response header) 中添加 Etag 的头,这个值表示 A 文件在服务器上的唯一标识。同时将 A 资源缓存到浏览器缓存中。

  2. 浏览器再次请求 A 资源的时候,会将 A 资源的 Etag 值放到请求头的 If-None-Match 里,再次请求服务器

  3. 服务器收到这个请求的时候,根据传过来的 Etag 值与自身服务器上该资源的 Etag 值进行对比,就能够很好判断资源相对客户端而言是否被修改过。

  4. 如果 Etag 一致,那么就返回 304 状态码和空的响应体。那浏览器根据 304 状态码,会直接去浏览器缓存中获取 A 资源。

  5. 而如果 Etag 匹配不上,说明文件有更新,于是返回新资源 A (和新的 Etag )和 200 状态码,并更新浏览器缓存。

Last-Modified 与 Etag 的对比

  1. Etag 精确度高于 Last-Modified, 只要文件修改 Etag 就会变,而 文件在 1秒 内多次改变,可能 Last-Modified 并不改。

  2. 性能上: Etag 需要服务端计算出文件的 hash, 而 Last-Modified 只需要记录时间,因此 Last-Modified 性能更好。

  3. 优先级上, Etag 优先级高于 Last-Modified

4. 缓存机制

强缓存优先于协商缓存,若强制缓存( Expires 和 Cache-Control ) 生效则直接使用,否则进行协商缓存( Last-Modified / If-Modified-Since 和 Etag / If-None-Match ), 协商缓存由服务器决定是否使用缓存,如果协商缓存失败,则返回 200, 重新返回资源和缓存标识。协商缓存生效则返回304, 使用缓存。

5. 实际场景策略

  1. 频繁变动的资源(文件内容更改,但是文件名称不改变)

Cache-Control: no-cache;

即使用 Cache-Control: no-cache; 使得忽略强制缓存,进入协商缓存阶段。协商缓存根据 Etag 或者 Modified-Since 来判断是否使用缓存。

查看了 www.baidu.com 和 www.trip.com, trip 的首页 www.trip.com/ 没有设置 Cache-Control,但是有 Etag,因此有时候 (可能是后端负载均衡的问题,不一定每次都落到同一台服务器) 会返回 304s。baidu 的首页设置 Cache-Control 为 private, Expires 为当前时间(即立即过期), 最终的结果是每次都返回 200

疑问: 比如 trip.com 的首页是 node端渲染模板引擎的结果,每一次请求都是创建一个新的 HTML文件,之前提到的服务端根据 Etag 或者 Last-Modified 去判断文件是否修改(像是更适用于静态资源),看起来应该都会认为被修改过才对。为什么还能被缓存。或者是不是因为中间的代理服务器缓存了文件的结果,实际上请求根本没到携程的服务器,而被中间代理服务器返回了

  1. 不常变化的资源(文件内容不常变化,如前端 *.js )

Cache-Control: max-age=***;

即设置一个较长时间的强缓存,使得前端强制缓存该文件。而如果需要更新文件,改变该文件内容,则在文件名中添加 hash 版本号,使得加载最新的文件。

参考资料

  1. 很多静态服务器,如 vscode live-server 扩展和 express.static 都会自动给静态文件添加缓存头,这很不利于我们测试缓存,因此我们需要一款更为纯粹简单的服务器。

Previousgithub travis ci 自动部署NextWEB 系统登录相关知识梳理

Last updated 4 years ago

Was this helpful?

主要参考:

补充参考:

测试服务器:

https://www.cnblogs.com/chenqf/p/6386163.html
深入理解浏览器的缓存机制
一文读懂前端缓存
node-server
高级前端进阶-前端面试题-木易杨