Web Storage (客户端存储)
从cookie 说起
-
Cookie 的本职工作并非本地存储,而是“维持状态”。
-
HTTP 协议是一个无状态协议,服务器接收客户端的请求,返回一个响应,故事到此就结束了,服务器并没有记录下关于客户端的任何信息。那么下次请求的时候,如何让服务器知道“我是我”呢?
在这样的背景下,Cookie 应运而生。
- 大家知道,Cookie 是有体积上限的,它最大只能有 4KB。当 Cookie 超过 4KB 时,它将面临被裁切的命运。这样看来,Cookie 只能用来存取少量的信息。
- cookie有一些限制,例如每个域名下最多只能存储20个cookie,每个cookie的大小不能超过4KB,且cookie可以被浏览器禁用或删除。
Web Storage
- 是 HTML5 专门为浏览器存储而提供的数据存储机制。它又分为 Local Storage 与 Session Storage。这两组概念非常相近,我们不妨先理解它们之间的区别,再对它们的共性进行研究。
Local Storage VS Session Storage
sessionStorage
- 为每一个给定的源(given origin)维持一个独立的存储区域。该存储区域在页面会话期间可用(即只要浏览器处于打开状态,包括页面重新加载和恢复)-会话存储。
localStorage
- 同样的功能,但是在浏览器关闭,然后重新打开数据仍然存在 - 持久化存储。
sessionStorage 和localStorage 一般统称为 Web Storage (API)
相同点
- 都遵循同源策略
- 容量一样
不同点
生命周期与作用域的不同。
-
生命周期:Local Storage 是持久化的本地存储,存储在其中的数据是永远不会过期的,使其消失的唯一办法是手动删除;而 Session Storage 是临时性的本地存储,它是会话级别的存储,当会话结束(页面被关闭)时,存储内容也随之被释放。
-
作用域:Local Storage、Session Storage 和 Cookie 都遵循同源策略。但 Session Storage 特别的一点在于,即便是相同域名下的两个页面,只要它们不在同一个浏览器窗口中打开,那么它们的 Session Storage 内容便无法共享。
webStorage注意事项
- 都是同步API,阻塞,如果存单个键或者值太大,影响DOM
- 存储的是字符串,要保存对象时,通常需要JSON.Stringgfy
webStorage是共享的吗
test1
<button type="button" id="btnAdd">添加</button>
<a href="./other.html" target="_blank">打开新页面</a>
test2
<button type="button" id="btnAdd">添加</button>
<a href="./other.html" target="_blank" rel="opener">打开新页面</a>
总结
- 打开多个相同的URL 的Tab,会创建各自的sessionStorage
- 通过a标签target="blank"跳转到新页面时 sessionStorage就会丢失。a标签添加属性rel="opener'"能够复制 。仅仅能复制,之后的更改并不会同步!!
- rel="opener' => 表示当打开链接文档时,应在与当前浏览上下文相关的新浏览上下文中打开它
webStorage过期支持
- 添加一个属性,记住过期时间
- 添加数据,一起保存
- 查询数据,对比时间,过期删除
代码
html
<button type="button" id="btnSetItem">添加</button>
<button type="button" id="btnGetItem">查询</button>
js
const myLocalStore = {
setItem: (key, value, expire) => {
const lsValue = JSON.parse(localStorage.getItem(key) || "{}");
localStorage.setItem(
key,
JSON.stringify({
...lsValue,
value,
expire,
})
);
},
getItem: (key) => {
// 在取值之前先判断是否过期
const lsValue = JSON.parse(localStorage.getItem(key) || "{}");
if (lsValue.expire && lsValue.expire >= Date.now()) {
return lsValue.value;
} else {
localStorage.removeItem(key);
return null;
}
},
};
btnSetItem.onclick = function () {
myLocalStore.setItem("key-x", "value-1", Date.now() + 10000);
};
btnGetItem.onclick = function () {
console.log("getItem:", myLocalStore.getItem("key-x"));
};
第三方库
- we-storage-cache
webStorage存储加密
AES 和 RSA
- 对称加密算法:只需要使用一个密钥可以加密,也可以解密。
- 非对称加密算法:有私钥和公钥之分,使用公钥加密之后,必须使用私钥才可以解密。
URL
encodeURLComponent + decodeURLComponent
base64
window.btoa + window.atob
注意事项
- 如果传入字符串不是有效的 base64 字符串,比如其长度不是 4 的倍数,其是中文时 则抛出DOMException。
// 编码
function utf8_to_b64(str) {
return window.btoa(unescape(encodeURIComponent(str)));
}
// 解码
function b64_to_utf8(str) {
return decodeURIComponent(escape(window.atob(str)));
}
复杂加密
Web Crypto API 的SubtleCrypto
html
<button type="button" id="btnEncrypt">加密</button>
<button type="button" id="btnDecrypt">解密</button>
js
<script>
let publicKey;
let privateKey;
let arrayBuffer;
(async function init() {
// 生成私钥(privateKey)和公钥(publicKey)
// 加密用公钥, 解密用私钥
const keyPair = await crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256",
},
true,
["encrypt", "decrypt"]
);
publicKey = keyPair.publicKey;
privateKey = keyPair.privateKey;
})()
/**
* 返回ArrayBuffer
*/
function encrypt(text, publicKey) {
// 字符串转为TypedArray
const clearText = new TextEncoder().encode(text)
return window.crypto.subtle.encrypt(
{
name: "RSA-OAEP"
},
publicKey,
// an ArrayBuffer, or a TypedArray
clearText
);
}
/**
* cipherText: ArrayBuffer
*
*/
async function decrypt(cipherText, privateKey) {
// cipherText 是ArrayBuffer
let decrypted = await window.crypto.subtle.decrypt(
{
name: "RSA-OAEP"
},
privateKey,
cipherText
);
const dec = new TextDecoder();
return dec.decode(decrypted);
}
btnEncrypt.onclick = async () => {
const text = textAreaClearText.value;
arrayBuffer = await encrypt(text, publicKey);
// ArrayBuffer转为字符串
const dec = new TextDecoder();
textAreaCipherText.value = dec.decode(arrayBuffer);
console.log("arrayBuffer:", arrayBuffer);
}
btnDecrypt.onclick = async () => {
const text = await decrypt(arrayBuffer, privateKey);
textAreaClearText.value = text
}
</script>
加密库
- crypro-js
第三方库
- secure-js
webStorage存储空间
采用什么字符编码
The keys and the values stored with
localStorageare always in the UTF-16DOMStringformat, which uses two bytes per character. As with objects, integer keys are automatically converted to strings.
localStorage 存储的键和值始终采用 UTF-16 DOMString 格式,每个字符使用两个字节。与对象一样,整数键将自动转换为字符串。
答案: UTF-16
存储是多大
localStorage 的容量限制因浏览器而异。在大多数现代浏览器中,localStorage 的容量限制约为 5MB - 10MB
5MB的单位是什么
- 字符的个数
- 字节数
- 字符的长度值 ✅
- bit 数
- utf-16编码单元 ✅
一个utf-16的编码单元是 两个字节,所以 是5M的字符的长度,10M的字节
键占不占空间
占
- 键名尽量短小
如何检测容量
navigator.storage.estimate()
.then(
function(storageEstimate) {
console.log(`可用空间大小:${storageEstimate.quota}`);
console.log(`已使用空间大小:${storageEstimate.usage}`); })
.catch(function(error) { console.error(error); });
注意点
-
navigator.storage.estimate()方法只是一个估计值,并不是一个精确的数值。实际上,大多数浏览器都会动态调整可用的存储空间,以适应用户的实际使用情况。因此,在编写代码时应该谨慎处理存储空间的估计值。 -
如果要存储大量的数据,请考虑使用其他存储方式,例如 IndexedDB 或 Web SQL Database。
应用场景
Local Storage
考虑到 Local Storage 的特点之一是持久,有时我们更倾向于用它来存储一些内容稳定的资源。比如图片内容丰富的电商网站会用它来存储 Base64 格式的图片字符串:
有的网站还会用它存储一些不经常更新的 CSS、JS 等静态资源。
Session Storage
Session Storage 更适合用来存储生命周期和它同步的会话级别的信息。这些信息只适用于当前会话,当你开启新的会话时,它也需要相应的更新或释放。比如微博的 Session Storage 就主要是存储你本次会话的浏览足迹: