Skip to content

浏览器的存储策略

1 本地存储与会话存储

sessionStorage本地存储localStorage会话存储是Web浏览器提供的两种用于在客户端存储数据的机制,它们都遵循同源策略(协议、域名、端口相同),以键值对(key: value)的形式存储数据,且存储的值==只能是字符串类型==(若存储其他类型,会自动转换为字符串,读取时需手动转换回原类型) 。

但在存储有效期、数据共享范围等方面存在差异:

特性sessionStoragelocalStorage
有效期仅在当前会话(浏览器标签页或窗口打开期间)有效,==关闭标签页或窗口后==,存储的数据会被自动清除除非主动调用代码删除或用户手动清除浏览器缓存,否则数据会==一直保留在本地==
数据共享范围==不同标签页或窗口==(即使是同一页面的不同实例)之间==无法共享==数据,只有在同一个标签页内通过window.open打开的新页面才能访问到相同的sessionStorage数据==同一域名下==的所有页面都可以访问和修改localStorage中的数据,不同标签页、窗口之间数据是共享的
应用场景适合临时存储一些仅在当前会话中使用的数据,比如多步骤表单中每一步的临时数据、当前会话的用户浏览记录等适用于需要长期保存的用户偏好设置(如主题设置、字体大小等)、用户登录状态的持久化存储等

常用方法

sessionStoragelocalStorage拥有相似的方法:

  • 存储数据(setItem:使用setItem方法可以向sessionStoragelocalStorage中添加键值对。
javascript
// 向 sessionStorage 中存储数据
sessionStorage.setItem('username', 'John'); 

// 向 localStorage 中存储数据
localStorage.setItem('theme', 'dark');
  • 读取数据(getItem:通过getItem方法获取存储的数据,如果指定的键不存在,则返回null
javascript
const username = sessionStorage.getItem('username'); 
const theme = localStorage.getItem('theme');
  • 删除数据(removeItem:使用removeItem方法可以删除指定键的数据。
javascript
sessionStorage.removeItem('username'); 
localStorage.removeItem('theme');
  • 清除所有数据(clearclear方法会一次性删除sessionStoragelocalStorage中存储的所有数据。
javascript
sessionStorage.clear(); 
localStorage.clear();

注意点

  • 数据类型转换:由于sessionStoragelocalStorage==只能存储字符串==,当存储对象、数组等复杂数据类型时,==需要使用JSON.stringify将其转换为字符串==,读取时再使用JSON.parse转换回原数据类型。
javascript
const user = { name: 'Alice', age: 30 };
// 存储对象
localStorage.setItem('user', JSON.stringify(user)); 

// 读取对象
const retrievedUser = JSON.parse(localStorage.getItem('user'));
  • 存储容量限制:不同浏览器对sessionStoragelocalStorage的存储容量限制有所不同,一般来说,==大约在5 - 10MB之间==。如果存储的数据超过了限制,会抛出QuotaExceededError异常 。
  • 安全性:它们存储在客户端,数据相对容易被获取,==不适合存储敏感信息==(如密码等)。同时,在使用localStorage时,要注意防止跨站点脚本攻击(XSS),因为攻击者可能会利用漏洞读取或修改localStorage中的数据。 Pasted image 20250903174959.png

用于在前端对后端发送请求时会带上cookie中的内容,但其存储在本地且可以直接访问查看的特性,使其不能存储用户的私密信息与账密

属性详解

javascript
// Cookie 格式:key=value; attributes
document.cookie = "username=john; domain=.example.com; path=/; max-age=3600; secure; samesite=strict";

[补充说明]:Cookie 主要属性包括:

  • key=value:键值对([原笔记:fatty,修正为键值对])
  • domain:作用域名
    • 设置为 .a.coma.com可作用于 a.com 及其所有子域名
    • 不设置和设置则仅作用于当前域名
  • path:URL路径限制(较少使用)
  • max-age:存活时间(秒),优先级高于expires
    • 正数:存活时间
    • 0:立即删除
    • 负数:会话结束时删除
  • expires:绝对过期时间(GMT格式)
  • secure:仅通过HTTPS传输
  • httponly:禁止JavaScript访问(增强安全性)
  • samesite:跨站请求限制
    • Strict:严格模式,完全禁止跨站发送
    • Lax:宽松模式,允许部分安全请求跨站发送
    • None:无限制(必须与secure同时使用)

CORS 跨域资源共享

[补充说明]:跨域请求处理机制:

  • 简单请求:直接发送,包含Origin头部
  • 非简单请求:先发送OPTIONS预检请求

服务器响应头部:

http
Access-Control-Allow-Origin: https://example.com  # 允许的源
Access-Control-Allow-Methods: GET, POST, PUT      # 允许的方法
Access-Control-Allow-Credentials: true            # 是否允许携带凭证
Access-Control-Max-Age: 86400                     # 预检请求缓存时间

3 Session

由于http是无状态的,使用Session ID来保存用户的信息。通过将用户的信息存储在服务器中,再生成一个session用来代表用户的登录状态: Pasted image 20251125123233.png 在收到服务器的相应之后,前端每次访问都会附带cookie中的内容

4 Token

JWT(JSON Web Token)用来解决Session存储在服务器以及在多个服务器中登录转态可能不一致的问题,Token是存储于用户端的: Pasted image 20251125123855.png

5 浏览器缓存实现

由于HTTP仅仅规定了缓存,但是没有规定如何实现,由浏览器自行实现 在强缓存的过程中,浏览器会将缓存的数据存储在不同的位置:

Memory Cache

Memory Cache 翻译过来便是“内存缓存”,顾名思义,它是存储在浏览器内存中的。其优点为获取速度快、优先级高,从内存中获取资源耗时为 0 ms,而其缺点也显而易见,比如生命周期短,当网页关闭后内存就会释放,同时虽然内存非常高效,但它也受限制于计算机内存的大小,是有限的。 Pasted image 20251205233829.png

Disk Cache

那么如果要存储大量的资源,这是还得用到磁盘缓存。

Disk Cache 翻译过来是“磁盘缓存”的意思,它是存储在计算机硬盘中的一种缓存,它的优缺点与 Memory Cache 正好相反,比如优点是生命周期长,不触发删除操作则一直存在,而缺点则是获取资源的速度相对内存缓存较慢。

Disk Cache 会根据保存下来的资源的 HTTP 首部字段来判断它们是否需要重新请求,如果重新请求那便是强缓存的失效流程,否则便是生效流程。

从两者的优缺点中我们可以发现,Memory Cache 与 Disk Cache 珠联璧合,优势互补,共同构成了浏览器本地缓存的左右手。

缓存获取顺序

因为目前市面上浏览器众多,不同浏览器的缓存机制都可能不同,还是以主流的 Chrome 为例进行介绍。

浏览器缓存机制包含了 Http 缓存中强缓存、协商缓存的知识点,这里就不再进行赘述,下面主要介绍与 Memory Cache、 Disk Cache 相关的机制。

按照缓存顺序来讲,当一个资源准备加载时,浏览器会根据其三级缓存原理进行判断。

  1. 浏览器会率先查找内存缓存,如果资源在内存中存在,那么直接从内存中加载
  2. 如果内存中没找到,接下去会去磁盘中查找,找到便从磁盘中获取
  3. 如果磁盘中也没有找到,那么就进行网络请求,并将请求后符合条件的资源存入内存和磁盘中

按照以上顺序,浏览器缓存与 HTTP 缓存才得以相辅相成,在有效的沟通和判断中尽可能的减少不必要的资源浪费。

缓存存储优先级

上述我们讲解了缓存资源的获取顺序,那么在获取之前,浏览器又是按照什么优先级来存储资源的?这一问题也可以直接换成“浏览器判断一个资源是存入内存缓存还是磁盘缓存的依据是什么?”。

其实答案在介绍内存缓存和磁盘缓存时已经有所涉及,我们以掘金首页为例子进行介绍。

当我们打开开发者工具并在浏览器输入 url 访问后,发现除了 base64 的图片永远从内存加载外,其他大部分资源会从磁盘加载。

20210914175010.jpg

磁盘缓存会将命中强缓存的 js、css、图片等资源都收入囊中,也省去我们担心它“挑食”的问题。

而内存缓存不这样,为了保持“苗条的身材”,它不得不控制“饮食”,尽可能的去挑选适合自己的“食物”。此时我们刷新下页面让内存缓存生效:

20210914220039.jpg

我们先过滤下只看 JS 资源的加载情况,发现有些被内存缓存了,有些则没有,这是为什么?

有些读者可能会猜测是不是没有被缓存的是因为资源比较大,其实不然,上方图片笔者圈出了 Initiator 列,通过该列便可以找到答案。

Initiator 列表示资源加载发起的位置,我们点击从内存获取资源的该列值后可以发现资源是在 HTML 渲染阶段就被加载的,如以下代码所示:

<!DOCTYPE html> <html lang="zh-CN"> <head> <title>Demo</title> <script src="https://i.snssdk.com/slardar/sdk.js"></script> </head> <body> <div id="cache">加载的 JS 资源大概率会存储到内存中</div> </body> </html>

而被内存抛弃的资源我们也可以发现其都是异步加载的资源,这些资源没有被内存缓存,比如像这样:

<!DOCTYPE html> <html lang="zh-CN"> <head> <title>Demo</title> </head> <body> <div id="cache">异步加载的 JS 资源没有存储到内存中</div> <script> window.onload = function () { setTimeout(function () { var s = document.createElement("script"); s.type = "text/javascript"; s.async = true; s.src = "https://i.snssdk.com/slardar/sdk.js"; var x = document.getElementsByTagName("script")[0]; x.parentNode.insertBefore(s, x); }, 2000); }; </script> </body> </html>

根据以上测试代码很容易产生错误的判断结论:异步加载的 JS 资源不会存储到内存中。

> 浏览器内存缓存生效的前提下,JS 资源的执行加载时间会影响其是否被内存缓存

我们可以修改上述的 setTimeout 时间为 1 秒后再次进行验证,大家会发现即使异步了,JS 资源还是很容易被内存缓存,原因便是异步 JS 资源加载时浏览器渲染进程可能还没有结束,而进程没结束就有被存入内存的可能。

此外图片资源(非 base64)也有和 JS 资源同样的现象,而 CSS 资源比较与众不同,其被磁盘缓存的概率远大于被内存缓存。

20210915153932.jpg

这一现象目前还没有找到标准的答案,网上给出的非标准解释是:

> 因为 CSS 文件加载一次就可渲染出来,我们不会频繁读取它,所以它不适合缓存到内存中,但是 JS 之类的脚本却随时可能会执行,如果脚本在磁盘当中,我们在执行脚本的时候需要从磁盘取到内存中来,这样 IO 开销就很大了,有可能导致浏览器失去响应。

以上所述的内存缓存(Memory Cache)在浏览器标准中并没有详尽的描述,笔者也是根据自身实践及总结得出的一些结论,不同的浏览器在加载资源时可能会所有差异,读者还需根据自己的理解和实践进行进一步探索。

Preload 与 Prefetch

基于上述现象的前提下,笔者还发现了与资源加载相关的两个功能(Preload 与 Prefetch)也会潜移默化的影响着浏览器缓存。

preload 也被称为预加载,其用于 link 标签中,可以指明哪些资源是在页面加载完成后即刻需要的,浏览器会在主渲染机制介入前预先加载这些资源,并不阻塞页面的初步渲染。例如:

<link rel="preload" href="https://i.snssdk.com/slardar/sdk.js" as="script" />

而当使用 preload 预加载资源后,笔者发现该资源一直会从磁盘缓存中读取,JS、CSS 及图片资源都有同样的表现,这主要还是和资源的渲染时机有关,在渲染机制还没有介入前的资源加载不会被内存缓存。

相反 prefetch 则表示预提取,告诉浏览器加载下一页面可能会用到的资源,浏览器会利用空闲状态进行下载并将资源存储到缓存中。

<link rel="prefetch" href="https://i.snssdk.com/slardar/sdk.js" />

使用 prefetch 加载的资源,刷新页面时大概率会从磁盘缓存中读取,如果跳转到使用它的页面,则直接会从磁盘中加载该资源。

利用好 preload 和 prefetch 这“两员大将”,我们可以优化浏览器资源加载的顺序和时机,在页面性能优化环节至关重要。