它们都是浏览器数据存储的方案,是用于解决数据持久化的问题。除此之外,数据也可以存储在内存中(比如挂载到 window
等全局对象下),但这种方式每当页面刷新就会丢失。
下面分别从几个方面,详细地介绍 Cookie
、sessionStorage
、localStorage
的区别。
1. 空间限制
-
Cookie
- 大小约为4K
。 -
sessionStorage
和localStorage
- 大小约为5M
。
请注意,其中 Cookie 的空间大小指的是 name
、value
以及 =
号。
另外,这三个不同浏览器下可能会有细微的差异,可忽略。
2. 数量限制
-
sessionStorage
和localStorage
无数量限制一说。 -
Cookie
是有数量限制的。
下表为网上收集(非当前实测结论),看一眼知道有限制这回事就好。
浏览器 | 大小 | 数量 |
---|---|---|
IE6 | 4095 个字节 | 每个域 20 个 |
IE7/8 | 4095 个字节 | 每个域 50 个 |
Opera | 4096 个字节 | 每个域 30 个 |
FireFox | 4097 个字节 | 每个域 50 个 |
Safari | 4097 个字节 | 没有数量限制 |
Chrome | 4097 个字节 | 每个域 53 个 |
鉴于各浏览器对 Cookie
的空间、数量限制不完全相同,为了较好地兼容,建议如下:
没错,面试官问到这么说吧,应该就 OK 了。
另外,与超出空间限制不同的是,超出数量限制之后,是可以继续添加 Cookie
的,但不同浏览器有不同的策略:一些是替换掉最先(老)的 Cookie
,有些则是随机替换。作简单了解就好,一般项目不会设那么多的,而且 Cookie
过期浏览器是会自动清除的。
3. 有效期
-
Cookie
- 有效期是由 Max-Age 或 Expires 决定的。当Cookie
过期或失效,由浏览器自动删除。- 若在设置
Cookie
时不写入这两个属性,那么它的就是会话级别的,即退出浏览器会被销毁。 - 若同时存在时,
Max-Age
优先级更高。
- 若在设置
-
SessionStorage
- 在浏览器标签(或窗口)关闭之前均有效。刷新页面不影响。 -
localStorage
- 若不主动清除,永久有效(“主动”是指由浏览器或脚本清除)。
4. 作用域
这块内容比较多...
铺垫 - 了解同源和同站的区别
区别如下:
铺垫 - 顶级域名和二级域名的认知
关于顶级域名、二级域名,也有很多认知问题。
在国内,很多资料认为,顶级域和一级域是分开的。比如 .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(网址)构成如下:
简单来说,只要二级域名相同,就属于同站。同源则要求更严格。因此同源一定同站,反之则不一定。
以下例子,同站,但不同源。
http://a.example.com:80
http://b.example.com:80
正题 - 三者的作用域
理解这些很重要,为什么这么说呢?
拓展 - 作用域引发的问题
很多公司,不是每个 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
的,反之也成立。如果 a
和 b
项目中某个 sessionStorage
的 key
不小心设置成相同的话,那很可能就会影响到对方。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_url
、page_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"
结论:
总结 - 作用域
5. 与服务器通信的差异
sessionStorage
和 localStorage
不会主动参与与服务器的通信。
Cookie 是保存在浏览器上的一小型文本文件。在每次与服务器的通信中都会携带在 HTTP 请求头之中。
6. 拓展(进阶)
Q1:storage 事件的迷惑行为
首先,注册 storage
事件监听器,它只能监听其他同源页面的缓存发生改变时,它才会被触发。
得出几个结论:
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 操作可能会抛出异常
Q4:sessionStorage 在 iframe 的问题
前面提到顶级窗口和 iframe 窗口的页面都是同源的情况下,sessionStorage 是可共享的。但不完全是,比如受 iframe
的 sandbox 属性影响,分为两种情况:
// 1️⃣
<iframe sandbox>
// 2️⃣
<iframe sandbox="allow-same-origin">
假设两个页面同源;情况一 sessionStorage
不共享,在 iframe
中是一个全新的 sessionStorage
对象。情况二则共享 sessionStorage
。