• 浅析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>
  • 相关阅读:
    (hdu 7.1.8)Quoit Design(最低点——在n一个点,发现两点之间的最小距离)
    [Windows]_[0基础]_[使用命令行工具dumpbin分析文件]
    《走开》反馈
    二分基础
    日历的问题C语言,C++(boost),python,Javascript,Java和Matlab实现
    Unity3D 游戏开发架构篇 ——性格一流的设计和持久性
    2015第54周四
    2015第54周三
    2015第54周二
    2015第54周一
  • 原文地址:https://www.cnblogs.com/goloving/p/16161207.html
Copyright © 2020-2023  润新知