Cookie 和 Storage API 区别与详解

本文涉及的产品
.cn 域名,1个 12个月
简介: Cookie 和 Storage API 区别与详解

前言


它们都是浏览器数据存储的方案,是用于解决数据持久化的问题。除此之外,数据也可以存储在内存中(比如挂载到 window 等全局对象下),但这种方式每当页面刷新就会丢失。

下面分别从几个方面,详细地介绍 CookiesessionStoragelocalStorage 的区别。


正文


1. 空间限制


  • Cookie - 大小约为 4K
  • sessionStoragelocalStorage  -  大小约为 5M

请注意,其中 Cookie 的空间大小指的是 namevalue 以及 = 号。

另外,这三个不同浏览器下可能会有细微的差异,可忽略。

在所有浏览器中,若 Cookie 大小已超出空间限制,后续设置的新 Cookie 就会被忽略。


2. 数量限制


  • sessionStoragelocalStorage 无数量限制一说。
  • Cookie 是有数量限制的。

下表为网上收集(非当前实测结论),看一眼知道有限制这回事就好。

浏览器 大小 数量
IE6 4095 个字节 每个域 20 个
IE7/8 4095 个字节 每个域 50 个
Opera 4096 个字节 每个域 30 个
FireFox 4097 个字节 每个域 50 个
Safari 4097 个字节 没有数量限制
Chrome 4097 个字节 每个域 53 个

若有兴趣,实测可以看这个网站:Browser Cookie Limits。作者简单跑了下,Chrome 每个域是 180 个;Firefox 很快卡住了,风扇嗡嗡响...... 测试结果 Firefox 和 Safari 都 N/A(没数据),应该是没数量限制吧。

鉴于各浏览器对 Cookie 的空间、数量限制不完全相同,为了较好地兼容,建议如下:

  • 总共 300 个 Cookie
  • 每个 Cookie 大小为 4096 个字节
  • 每个域 20 个 Cookie
  • 每个域 81920 个字节(20 × 4096) 。

没错,面试官问到这么说吧,应该就 OK 了。

另外,与超出空间限制不同的是,超出数量限制之后,是可以继续添加 Cookie 的,但不同浏览器有不同的策略:一些是替换掉最先(老)的 Cookie,有些则是随机替换。作简单了解就好,一般项目不会设那么多的,而且 Cookie 过期浏览器是会自动清除的。


3. 有效期


  • Cookie - 有效期是由 Max-Age 或 Expires 决定的。当 Cookie 过期或失效,由浏览器自动删除。
  • 若在设置 Cookie 时不写入这两个属性,那么它的就是会话级别的,即退出浏览器会被销毁。
  • 若同时存在时,Max-Age 优先级更高。
  • SessionStorage - 在浏览器标签(或窗口)关闭之前均有效。刷新页面不影响。
  • localStorage - 若不主动清除,永久有效(“主动”是指由浏览器或脚本清除)。

请注意:

讲道理的话,会话级的 Cookie 在浏览器退出时就会被删除。但是有些浏览器不讲武德,比如 Chrome。在某些情况下,关闭浏览器重新进入,会话级别的 Cookie 并不会删除,在 Application 选项的 Cookie 栏还能看到。

原因可能是:Chrome - 设置 - 启动时 选择了 打开新标签 之外的选项。切换过来就好了...


4. 作用域


这块内容比较多...


铺垫 - 了解同源和同站的区别

区别如下:


  • 同源(Same-Origin):协议(protocol)+ 主机名(hostname)+ 端口(port)完全一致。
  • 同站(Same-Site):eTLD + 1 完全一致。


铺垫 - 顶级域名和二级域名的认知


关于顶级域名、二级域名,也有很多认知问题。

  • 顶级域名:也称为“一级域名”,常见的有 .com.cn.org.net 等等,需要注意的是像 .com.cn.com.hk 也属于顶级域名。
  • 二级域名:就是顶级域名的下一级域名。


在国内,很多资料认为,顶级域和一级域是分开的。比如 .baidu.com,如果按照这种方式划分,那么.com 是顶级域名,.baidu.com 就是一级域名。好像阿里云就是这样定义的。


而我更偏向于认为,.baidu.com 属于二级域名。不用过分纠结,在团队内统一即可。

eTLD(Top-Level Domains)表示有效顶级域名,那么 eTLD + 1 就表示二级域名。例如:

https://www.example.com.cn


其中 eTLD.com.cn,那么 eTLD + 1 就是 .example.com.cn。关于 eTLD 更多请看这里


铺垫 - 区分同源和同站


先看下,完整的 URL(网址)构成如下:


22.webp.jpg

简单来说,只要二级域名相同,就属于同站。同源则要求更严格。因此同源一定同站,反之则不一定。

以下例子,同站,但不同源。

http://a.example.com:80
http://b.example.com:80


正题 - 三者的作用域


  • localStorage - 这个最简单,必须同源才能访问,在不同标签(或窗口)之间可共享。
  • sessionStorage - 必须同源,且不同标签(或窗口)之间是不能共享的(这点上盲猜挺多人会理解错的,我当初也理解错了)。
  • Cookie - 同站是前提,它还受限于具体的 DomainPath

理解这些很重要,为什么这么说呢?


拓展 - 作用域引发的问题


很多公司,不是每个 Web 项目对应一个三级域名。

// 主营业务、较为重要的业务,可能会这样去区分:
https://project-a.company.com
https://project-b.company.com
// 但一些非业务性、或者一些小项目,很可能是这样的:
https://sub.company.com/project-a/index.html
https://sub.company.com/project-b/index.html


这些都很常见,那么问题来了。它们很多都是同源、同站的。假设按小项目划分场景,我在 a 项目中设置了一个 sessionStorage 会话级缓存,那么当前从 a 项目跳转至 b 项目时,b 是可以获取到 a 项目的所有 sessionStorage 的,反之也成立。如果 ab 项目中某个 sessionStoragekey 不小心设置成相同的话,那很可能就会影响到对方。localStorage 同理。至于 Cookie 的话,由于它的空间限制最大只允许 4K,因此不适宜存过多数据,一般会存一些像鉴权信息等比较多。同个公司,业务的用户鉴权等是相似的,所以 Cookie 的访问机制也不会有太大的影响。

针对这些问题,建议是非必要的话,将数据存在内存中,比如用 Vuex、Redux、MobX 等状态管理工具来维护应用的状态。一是信息更不容易暴露,而是可以减少 IO 的读写。但是,这样的话,就要解决数据持久化的问题,因为在内存中的话,只要刷新页面就会丢失。怎么解决?

以 Redux 为例,在创建 Store 时,是可以传入一个初始状态的,它的值取下面这个会话缓存即可。只要监听到状态发生变化变化,并设置或更新 sessionStorage 级别的缓存,将状态缓存起来即可。比如:

import { createStore } from 'redux'
// 定义一个可按项目划分的 key
const { host, pathname } = window.location
const stateCacheKey = `cache_state_${host}_${pathname}`
// 设置 store 的初始值,取 stateCacheKey 缓存的值,首次为空对象
const initialState = JSON.parse(sessionStorage.getItem(stateCacheKey)) || {}
// 创建 Store(reducers、middlewares 非本文讨论重点省略...)
const reducers = (state = {}, action) => { /* some reducers... */ }
const store = createStore(reducers, initialState) // 这里省略了中间件
// 监听状态,每次状态变化 stateCacheKey 都得以更新
const unsubscribe = store.subscribe(() => {
  const currentStateStr = JSON.stringify(store.getState())
  window.sessionStorage.setItem(stateCacheKey, currentStateStr)
})
// 解除监听,这样 store.unsubscribe() 调用即可
store.unsubscribe = unsubscribe
// 个人习惯,也将 store 挂载全局,以备特殊情况调用
window.store = store
// 作为模块导出,并传入 react-redux 的 Provider 组件
export default store


注意 - sessionStorage 鲜为人知的点(冷门)


下面例子,均在同源情况下。假设有两个同源页面: A 页面、B 页面对应 URL 为 page_a_urlpage_b_url

示例一:

// 1. 在 A 页面,设置一个会话缓存:keyA
sessionStorage.setItem('keyA', '123')
// 2. 若 A 有一个链接,可跳转至 B 页面(将会以新窗口的形式打开 B 页面)
<a target="_blank" href="page_b_url">To Page B</a>
// 3. 点击链接,跳转到 B 页面
sessionStorage.getItem('keyA') // ❓ 打印结果是什么?
// 4. 接着,在 B 页面中,设置另一个会话缓存:keyB
sessionStorage.setItem('keyB', '456')
// 5. 切换至 A 页面,打印一下:
sessionStorage.getItem('keyB') // ❓ 打印结果又是什么?
// 6. 在 A 页面中再次设置一个缓存:keyB
sessionStorage.setItem('keyB', '789')
// 7. 再次切换至 B 页面的窗口,
sessionStorage.getItem('keyB') // ❓ 打印结果是 "456" 还是 "789" 呢?


示例二:将上述第二步改成下面这样:

<a onclick="window.open('page_b_url', 'DescriptiveWindowName')">To Page B</a>


结果又是什么呢?直接看下结果:

示例一,依次打印出:null、null、"456"
示例二,依次打印出:"123"、null、"456"


结论:


  • 同一标签(或窗口)下,所有同源页面将共享 sessionStorage,同样地,在某个页面修改,将影响其他页面。
  • 通过 <a target="_blank" href="page_b_url"></a>window.open('page_b_url', 'windowName') 方式打开其他同源页面,有以下特点:
  • 两个 Tab 之间的 sessionStorage 是独立的,互不影响。
  • 区别点在于,打开新窗口时,初始缓存不一样:前者的初始 sessionStorage 缓存为空。后者基于原 Tab 的 sessionStorage 拷贝一份,作为新窗口的初始 sessionStorage 缓存。
  • 前者表现与手动创建新窗口是一致的。
  • 需要另外一种情况,当在某页面内嵌套了一个同源的 iframe,它们之间 sessionStorage 是共享的。若非同源页面则不共享。(这一点不完全严谨,具体原因请往下看)


总结 - 作用域


  • Cookie 作用域前提是同站,同时还受限于 DomainPath。若两者一致,即可理解为同站共享。
  • sessionStoragelocalStorage 前提必须是同源,其次前者在不同标签(或窗口)相互独立;后者在所有标签之间共享。
  • 除此之外,以不同方式创建新标签(或窗口),它的 sessionStorage 初始值会有所差异。通过 window.open() 方式,会将原来先的 sessionStorage 值拷贝过来,作为其初始值;其他方式初始缓存为空。但注意,后续的 sessionStorage 读写操作都是独立对。


5. 与服务器通信的差异


sessionStoragelocalStorage 不会主动参与与服务器的通信。

Cookie 是保存在浏览器上的一小型文本文件。在每次与服务器的通信中都会携带在 HTTP 请求头之中。

但是会有一些限制,另起一文,请稍等...


6. 拓展(进阶)


Q1:storage 事件的迷惑行为

首先,注册 storage 事件监听器,它只能监听其他同源页面的缓存发生改变时,它才会被触发。

得出几个结论:

  • 用于监听其他页面的缓存变化,而同一页面内的缓存变化,都不起作用。(奇葩吧)
  • 由于不同 Tab 之间 sessionStorage 是独立的,因此无法监听 sessionStorage 的变化,即只能监听 localStorage 的变化。
  • “发生改变”包括:创建、更新、删除。但重复设置相同的 key-value 不会触发该事件,它至多在首次创建时触发。

const listener = function (e) {
  // key: 对应缓存 key
  // newValue: 该 key 新设置的缓存值
  // oldValue: 该 key 对应的旧缓存值
  // storageArea: 对应为 window.localStorage 对象
  // storageArea: 触发该事件所对应的 URL
}
window.addEventListener('storage', listener)


Q2:当 localStorage 超过 5M 的空间限制之后,若再次 setItem 会怎样?

答案显而易见,这次 setItem() 将会失败,且会抛出错误。针对这种情况,可以做一些处理,比如清空再重新记录等...

for (let i = 0; i < 2; i++) {
  try {
    localStorage.setItem('key', 'value')
    break
  } catch (e) {
    // 清空,并重试
    // QuotaExceededError: The quota has been exceeded. localStorage缓存超出限制
    localStorage.clear()
  }
}


Q3:在 Safari 无痕模式下,对 sessionStorage 操作可能会抛出异常

请看这个帖子:html5 localStorage error with Safari: "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage that exceeded the quota."

Q4:sessionStorage 在 iframe 的问题

建议少用 iframe,尽管目前很多大网站仍然使用它。

前面提到顶级窗口和 iframe 窗口的页面都是同源的情况下,sessionStorage 是可共享的。但不完全是,比如受 iframesandbox 属性影响,分为两种情况:

// 1️⃣
<iframe sandbox>
// 2️⃣
<iframe sandbox="allow-same-origin">


假设两个页面同源;情况一 sessionStorage 不共享,在 iframe 中是一个全新的 sessionStorage 对象。情况二则共享 sessionStorage


7. References


目录
相关文章
|
24天前
|
编译器 API 定位技术
API和SDK的区别
API 和 SDK 的区别在于:API 是一组定义了软件组件之间交互规范的接口,用于实现不同软件组件之间的通信;而 SDK 是一个全面的工具集合,包含 API、编译器、调试器、文档等,用于特定平台的应用程序开发。SDK 范围更广,内容更丰富,更具体和具象化,适合复杂的开发需求;API 则更加抽象,侧重于功能的定义和调用方式。
|
1月前
|
人工智能 监控 负载均衡
一文详述:AI 网关与 API 网关到底有什么区别?
近年来,AI发展迅猛,大模型成为推动业务创新的关键力量。企业面临如何安全管理和部署AI应用的挑战,需设计既能满足当前需求又可适应未来发展的基础架构。AI网关应运而生,在集成、管理和优化AI应用中扮演重要角色。本文探讨AI网关与API网关的区别,分析AI系统为何需要专门网关,并提供选择合适AI网关的建议。AI网关不仅支持多种模型,还具备高级安全性和性能优化功能,有助于企业在复杂环境中灵活应用AI技术。
78 1
|
19天前
|
存储 安全 数据安全/隐私保护
Cookie 和 Session 的区别及使用 Session 进行身份验证的方法
【10月更文挑战第12天】总之,Cookie 和 Session 各有特点,在不同的场景中发挥着不同的作用。使用 Session 进行身份验证是常见的做法,通过合理的设计和管理,可以确保用户身份的安全和可靠验证。
17 1
|
28天前
|
存储 JavaScript 前端开发
vuex和localstorage . cookie的区别
【10月更文挑战第8天】
48 1
|
22天前
|
编译器 API 定位技术
API和SDK的区别
API(应用程序编程接口)和SDK(软件开发工具包)的主要区别在于范围、内容、抽象程度及使用方式。API定义了软件组件间的交互规则,范围较窄,更抽象;而SDK提供了一整套开发工具,包括API、编译器、调试器等,范围广泛,具体且实用,有助于提高开发效率。
|
1月前
|
JavaScript 前端开发 安全
|
1月前
|
存储 缓存 JavaScript
cookie和localStorage的区别特点
cookie和localStorage的区别特点
88 0
|
3月前
|
存储 机器学习/深度学习 API
【Azure 存储服务】记一次调用Storage Blob API使用 SharedKey Authorization出现的403错误
【Azure 存储服务】记一次调用Storage Blob API使用 SharedKey Authorization出现的403错误
|
3月前
|
存储 JSON API
【Azure 存储服务】使用REST API操作Azure Storage Table,删除数据(Delete Entity)
【Azure 存储服务】使用REST API操作Azure Storage Table,删除数据(Delete Entity)
【Azure 存储服务】使用REST API操作Azure Storage Table,删除数据(Delete Entity)
|
3月前
|
XML 安全 API
REST 和 SOAP API 有什么区别?
【8月更文挑战第31天】
161 0