背景
Web应用的发展,使得客户端存储使用得也越来越多,而实现方式也是多种多样。最简单而且兼容性最佳的方案是Cookie,但是作为真正的客户端存储,Cookie 则存在很多致命伤。
- Cookie 数量和长度的限制。每个
domain最多只能有20条cookie,每个 Cookie 长度不能超过4KB, 否则会被截掉。 - 安全性问题。如果 Cookie 被人拦截了,对方就可以取得所有的 session 信息。即使加密也于事无补。因为拦截者并不需要知道 Cookie 的意义,他只要原样转发 Cookie 就可以达到目的了。
- 有些状态不可能保存在客户端。例如,为了防止重复提交表单,我们需要在服务器端保存一个计数 器。如果我们把这个计数器保存在客户端,那么它起不到任何作用。
- Cookie 不可以跨域调用。客户端每请求一个新的页面时 Cookie 都需要指定作用域被发送过去,无形中浪费了带宽。
注意:Cookie 虽然有以上缺点,但他依旧是不可或缺的。Cookie的作用是与服务器进行交互,作为HTTP规范的一部分而存在,而 Web Storage 仅仅是为了在本地“存储”数据而生。
浏览器本地存储
Web Storage 与 Cookie 对比而言是为了更大容量存储设计的,Web Storage 拥有 setItem,getItem,removeItem,clear 等方法,不像cookie需要前端开发者自己封装 setCookie,getCookie,操作数据的增删改查更加便捷。
sessionStorage VS localStorage
sessionStorage为每一个给定的源维持一个独立的存储区域,该存储区域在页面会话期间可用(即只要浏览器处于打开状态,包括页面重新加载和恢复)。localStorage有同样的功能,但是在浏览器关闭,然后重新打开后数据仍然存在。sessionStorage和localStorage一般统称为 Web Storage (API)sessionStorage和localStorage都遵循同源策略,容量由客户端程序(浏览器)决定,一般而言,通常是1MB~5MB左右。
web Storage 注意事项
- web Storage 是同步API,是阻塞型的,如果存储的键值对太大,会影响用户的使用体验
- web Storage 存储的是字符串,要保存对象的时候,需要转为字符串时通常使用
JSON.stringify进行序列化
JSON.stringify 缺点:
- 对象中有时间类型的时候,序列化之后会变成字符串类型。
- 对象中有undefined和Function类型数据的时候,序列化之后会直接丢失。
- 对象中有NaN、Infinity和-Infinity的时候,序列化之后会显示 null。
- 对象循环引用的时候,会直接报错。
sessionStroage是共享的吗?
sessionStroage 不是共享的,在新标签或窗口打开一个页面时会复制顶级浏览会话的上下文作为
新会话的上下文,打开多个相同的URL的 Tabs 页面,会创建各自的 sessionStorage 。
chrome浏览器89版本后,通过a标属性target="blank"跳到新页面时 sessionStorage就会丢失。a标签添加属性 rel="opener" 能够复制。仅仅能复制,之后的更改并不会同步!!
StorageEvent
当前页面使用的 storage 被其他页面修改时会触发 Storage Event 事件。
Storage Event 事件在同一个域下的不同页面间触发,即在A页面注册了 storge 的监听处理,只有在跟A同域名下的B页面操作 storage 对象,A页面才会被触发 storage 事件,B页面本身不会触发事件。
sessionStorage能触发 StorageEvent事件嘛?
能触发,但是有以下两种情况:
- a标签打开:不触发(demo1)
- iframe嵌套:触发(demo2)
举例说明demo1:
//index.html
<body>
<button type="button" id="btnAdd">添加</button>
<a href="./other.html" target="_blank" rel="opener">打开新页面</a>
<script>
let index = 1;
btnAdd.onclick = function() {
console.time(`key-1`)
sessionStorage.setItem('key-1',"值-1")
console.timeEnd(`key-1`)
index++
}
</script>
</body>
//other.html
<body>
<div>
<div>消息:</div>
<div id="message"></div>
</div>
<script>
window.addEventListener("storage",function(ev){
message.innerHTML = ev.newValue;
})
</script>
</body>
举例说明dem02:
//index.html
<body>
<button type="button" id="btnAdd">添加</button>
<iframe src="./other.html"></iframe>
<script>
let index = 1;
btnAdd.onclick = function() {
console.time(`key-1`)
sessionStorage.setItem('key-1',`值-${index}`)
console.timeEnd(`key-1`)
index++
}
</script>
</body>
如何区分 StorageEvent 事件是谁触发的
可以通过storageArea来判断到底是sessionStorage 触发的还是 localStorage 触发的。
localStorage支持过期
简单的实现
- 添加一个属性,记住过期的时间;
- 添加数据的时候,一起保存;
- 查询数据,比对事件,过期删除。
<body>
<button type="button" id="btnSetItem">添加</button>
<button type="button" id="btnGetItem">查询</button>
<script>
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'));
}
</script>
</body>
第三方库
- web-storage-cache
var wsCache = new WebStorageCache();
// 缓存字符串‘wqteam’到‘username’中,超时时间100秒
wsCache.set('username','wqteam',{exp:100});
//超时截止日期,可使用Date类型
var nextYear = new Date();
nextYear.setFullYear(nextYear.getFullYear() + 1);
wsCache.set('username','wqteam',{exp:nextYear});
localStorage存储加密
简单加密
- URL方式 : encodeURIComponent 、 decodeURIComponent
// 编码(将 Unicode 字符串编码为 UTF-8)
var unicodeString = 'Hello, 你好';
var utf8String = encodeURIComponent(unicodeString);
console.log(utf8String); // 输出:Hello%2C%20%E4%BD%A0%E5%A5%BD
// 解码(将 UTF-8 编码还原为 Unicode 字符串)
var decodedString = decodeURIComponent(utf8String);
console.log(decodedString); // 输出:Hello, 你好
- base64:window.btoa + window.atob
var password = "***$$$ABCC"
var val = btoa(password) //KioqJCQkQuJDQw==
var oriVal = atob(val) //***$$$ABCC
复杂加密
- Web Crypto API 的SubtleCrypto 接口提供了许多底层加密功能
使用案例
使用加密库
- crypto-js
var CryptoJs = require("crypto-js");
var data = [{id:1},{id:2}]
// Encrypt
var ciphertext = CryptoJs.AES.encrypt(JSON.stringify(data),'secret key 123').toString();
// Decrypt
var bytes = CryptoJs.AES.decrypt(ciphertext,'secret key 123');
var decrypteData = JSON.parse(bytes.toString(CryptoJs.enc.Utf8));
console.log(decrypteData)
第三方库
- secure-ls
- localstorage-slim
web Storage的存储空间
localstorage 存储的键值采用什么字符编码?
UTF-16,每个字符使用两个字节,是有前提条件的,就是码点小于OxFFFF(65535),大于这个码点的是四个字节。
5M 的单位是什么
- 字符的长度值,utf-16的编码单元
- 字符的个数,并不等于字符的长度
- 2个字节作为一个utf-16的字符编码单元,也可以说是5M 的utf-16的编码单元
localStorage 键占不占存储空间
答案:占,在存储key-value时应该使用简洁明了的设置key名称,为value节省空间。
<body>
<button type="button" id="btnSave">保存</button>
<script>
btnSave.onclick = function () {
const charTxt = "a";
let count = 2.5 * 1024 * 1024; //给键值对都设定2.5M长度
let content = Array.from({ length: count }, (_) => charTxt).join("");
const key = Array.from({ length: count }, (_) => charTxt).join("");
localStorage.clear();
try {
console.time("setItem");
// 此时如果给content再多设置一位字符就会报错
localStorage.setItem(key, content);
console.timeEnd("setItem");
} catch (err) {
console.log("err code:", err.code);
console.log("err message:", err.message);
}
};
</script>
</body>