浅析localStorage存储的字符编码、存储大小限制及单位、键值是否占空间、键值数量是否影响读写性能、如何统计localStorage已使用空间、如何用iframe扩容、如何跨域传递数据
localStorage 存储我们经常使用,但是你有没有深入思考下面这些问题呢?
(1)localStorage 存储的键值采用什么字符编码
(2)5M 的单位是什么
(3)localStorage 键占不占存储空间
(4)localStorage的键的数量,对写和读性能的影响
(5)写个方法统计一个localStorage已使用空间
一、localStorage 存储的键值采用什么字符编码?
我们先看 MDN 的描述:The keys and the values stored with localStorage
are always in the UTF-16 `DOMString`[2] format, which uses two bytes per character. As with objects, integer keys are automatically converted to strings.
翻译成中文:localStorage 存储的键和值始终采用 UTF-16 DOMString 格式,每个字符使用两个字节。与对象一样,整数键将自动转换为字符串。
答案:UTF-16
MDN这里描述的没有问题,也有问题,因为UTF-16,每个字符使用两个字节,是有前提条件的,就是码点小于0xFFFF
(65535), 大于这个码点的是四个字节。这是全文的关键。
二、5M 的单位是什么
5M的单位是什么?选项:- 字符的个数
- 字节数
- 字符的长度值
- bit 数
- utf-16编码单元
字符的个数,并不等于字符的长度,这一点要知道:
"a".length // 1
"人".length // 1
"??".length // 2
"??".length // 2
现代浏览器对字符串的处理是基于UTF-16 `DOMString`。但是说5M字符串的长度,显然有那么点怪异。而根据 UTF-16编码规则,要么2个字节,要么4 个字节,所以不如说是 10M 的字节数,更为合理。当然,2 个字节作为一个 utf-16 的字符编码单元,也可以说是 5M 的 utf-16 的编码单元。
现代浏览器的情况下:
所以,说是10M 的字节数,更为准确,也更容易让人理解。
如果说5M,那其单位就是字符串的长度,而不是字符数。
答案:字符串的长度值, 或者utf-16的编码单元。更为合理的答案是 10M 的字节空间。
三、localStorage 键占不占存储空间
我们把 key 和 value 各设为 2.5M 的长度,执行正常
const charTxt = "a";
let count = (2.5 * 1024 * 1024);
let content = new Array(count).fill(charTxt).join("");
const key = new Array(count).fill(charTxt).join("");
localStorage.clear();
try {
console.time("setItem")
localStorage.setItem(key, content);
console.timeEnd("setItem")
} catch (err) {
console.log("err code:", err.code);
console.log("err message:", err.message)
}
然后把 vaule 的长度加 1,再存储,发现执行失败,存储异常,说明 key 和 value 一起占空间。
四、localStorage的键的数量,对写和读性能的影响
设置 500*1000 个键
let keyCount = 500 * 1000;
localStorage.clear();
for (let i = 0; i < keyCount; i++) {
localStorage.setItem(i, "");
}
setTimeout(() => {
console.time("save_cost");
localStorage.setItem("a", "1");
console.timeEnd("save_cost");
}, 2000)
setTimeout(() => {
console.time("read_cost");
localStorage.getItem("a");
console.timeEnd("read_cost");
}, 2000)
// save_cost: 0.05615234375 ms
// read_cost: 0.008056640625 ms
// 单独执行保存代码:
localStorage.clear();
console.time("save_cost");
localStorage.setItem("a", "1");
console.timeEnd("save_cost");
// save_cost: 0.033203125 ms
key 很多,影响是有的,但是影响不大。
反过来,如果保存的值特别大
const charTxt = "a";
const count = 5 * 1024 * 1024 - 1
const val1 = new Array(count).fill(charTxt).join("");
setTimeout(() =>{
localStorage.clear();
console.time("save_cost_1");
localStorage.setItem("a", val1);
console.timeEnd("save_cost_1");
},1000)
setTimeout(() =>{
localStorage.clear();
console.time("save_cost_2");
localStorage.setItem("a", "a");
console.timeEnd("save_cost_2");
},1000)
// save_cost_1: 12.276123046875 ms
// save_cost_2: 0.010009765625 ms
可以多次测试,看到:单次值的大小对存的性能影响非常大,读取也是一样。所以尽量不要保存太大的值。
答案:键的数量对读取性能有影响,但影响不大;值的大小对性能影响更大,不建议保存大的数据。
五、写个方法统计一个localStorage已使用空间
function sieOfLS() {
return Object.entries(localStorage).map(v => v.join('')).join('').length;
}
将 obj 的 key 和 value 转换成了 [ [key, value], [key, value] ],然后 map 将 key 和 value 拼接,计算 length 就为已使用的空间
六、使用iframe给页面的localStorage扩容
浏览器提供的 localStorage 本地存储的最大空间是5M,如果不够用呢,这时候就需要考虑来给localStorage扩容。可看这篇文章:https://www.cnblogs.com/cherishSmile/p/8390754.html
思路如下:
(1)在【A域】下引入【B域】,【A域】空间足够时,读写由【A域】来完成,数据存在【A域】下;当【A域】空间不够时,读写由【B域】来完成,数据存在【B域】下
(2)【A域】空间不够需要在【B域】读写时,通过postMessage 向【B域】发送跨域消息,【B域】监听跨域消息,在接到指定的消息时进行读写操作
(3)【B域】接到跨域消息时,如果是写入删除可以不做什么,如果是读取,就要先读取本域本地数据通过postMessage向父页面发送消息
(4)【A域】在读取【B域】数据时就需要监听来自【B域】的跨域消息
注意事项:
(1)window.postMessage()方法,向【B域】发消息,应用window.frames[0].postMessage()
这样 iframe 内的【B域】才可以接到
(2)同理,【B域】向 【A域】发消息时应用,window.parent.postMessage()
(3)【A域】的逻辑一定要在 iframe 加载完成后进行
具体代码流程如下:
1、【A域】的页面如下:index.html
"utf-8" />//嵌入【B域】的一个空页面
由于需要判断【A域】的空间是否足够,所以需要计算【A域】已经被占用的空间。那么localStorage中的字符串以什么编码格式存储的呢?经过上面介绍,我们知道
localStorage中的字符串是以utf-16进行编码的。
2、【B域】的页面如下:storage.html
"utf-8" />
七、如何使用 postMessage+iframe 实现跨域的数据读取
1、postMessage(data, origin) 方法允许来自不同源的脚本采用异步方式进行通信,可以实现跨文本档、多窗口、跨域消息传递。接受两个参数:
data:要传递的数据,可以是JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器支持任意类型的参数,部分浏览器只能处理字符串参数,所以在传递参数时需要使用JSON.stringify()方法对对象参数序列化。
origin:字符串参数,指明目标窗口的源,协议+主机+端口号[+URL],URL会被忽略,所以可以不写,只是为了安全考虑,postMessage()方法只会将message传递给指定窗口,当然也可以将参数设置为"*",这样可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。
2、解决思路
通过postMessage 向【其他域】发送跨域消息;
window.parent.postMessage()
iframe.contentWindow.postMessage()
监听跨域消息 window.addEventListener('message', fn );
//【test1域下】 http://www.test1.com/index_a.htmlStatus
"http://www.test2.com/index_b.html">去index_b查看结果 // 【test2域下】 http://www.test2.com/getmessage.html // 【test2域下】http://www.test2.com/index_b.html点击获取任务