浅析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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<button class="set">存储</button>
<button class="get">读取</button>
<button class="remove">删除</button>
<iframe src="http://localhost:8000/storage.html"></iframe> //嵌入【B域】的一个空页面
</body>
<script src="js/localStorage.js"></script>
</html>
由于需要判断【A域】的空间是否足够,所以需要计算【A域】已经被占用的空间。那么localStorage中的字符串以什么编码格式存储的呢?经过上面介绍,我们知道
localStorage中的字符串是以utf-16进行编码的。
2、【B域】的页面如下:storage.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
</body>
<script type="text/javascript">
var fn = function(){};
fn.prototype = {
setLocal:function(key,value){
localStorage.setItem(key,value);
},
getLocal:function(key){
return localStorage.getItem(key);
},
removeLocal:function(key){
localStorage.removeItem(key);
},
bindEvent:function(){
var self = this;
//监听
window.addEventListener('message', function(e){
if(window.parent!=e.source) return;
var option = JSON.parse(e.data);
if(option.type.toLowerCase()=="get"){
var data = self.getLocal(option.key);
window.parent.postMessage(data,'*');
}
option.type.toLowerCase()=="set"&&self.setLocal(option.key,option.value);
option.type.toLowerCase()=="remove"&&self.removeLocal(option.key);
})
},
init:function(){
var self = this;
self.bindEvent();
}
}
var tools = new fn();
tools.init();
</script>
七、如何使用 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.html
<body>
<h2>Status</h2>
<p></p>
<a href="http://www.test2.com/index_b.html">去index_b查看结果</a>
<iframe src="http://www.test2.com/getmessage.html" frameborder="0"></iframe>
<script>
window.onload = function(){ //在页面加载完成后主页面向iframe发送请求
window.frames[0].postMessage('jogging, reading and writing', 'http://www.test2.com');
}
window.addEventListener('message', function(e){ // 主页面监听message事件
var data = e.data;
document.querySelector('p').innerHTML = data;
}, false);
</script>
</body>
// 【test2域下】 http://www.test2.com/getmessage.html
<body>
<script>
window.addEventListener('message', function(e) { //iframe接收消息,并把当前状态发送给主页面
if (e.source != window.parent)
return;
console.log(e.data);
localStorage.setItem('task', e.data);
window.parent.postMessage('finished', '*');
}, false);
</script>
</body>
// 【test2域下】http://www.test2.com/index_b.html
<body>
<div>点击获取任务</div>
<p></p>
<script>
document.querySelector('div').addEventListener('click', function(){
document.querySelector('p').innerHTML = localStorage.getItem('task');
}, false);
</script>
</body>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
2020-04-19 nodejs向加密文件指定位置插入内容
2020-04-19 NodeJS之crypto模块公钥加密及解密
2020-04-19 Node嵌入式数据库NeDB及遇到问题
2018-04-19 ElementUI使用问题记录:设置路由+iconfont图标+自定义表单验证