浅析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的单位是什么?选项:
  1. 字符的个数
  2. 字节数
  3. 字符的长度值
  4. bit 数
  5. utf-16编码单元
  以前不知道,现代浏览器,准确的应该是 选项3,字符的长度值 ,亦或 选项5, 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>

 

posted @ 2022-04-19 22:03  古兰精  阅读(1103)  评论(0编辑  收藏  举报