如何在HTML5的localStorage和sessionStorage中存储对象

如何在HTML5的localStorage和sessionStorage中存储对象

技术教程gslnedu2025-06-20 16:50:234A+A-

技术背景

HTML5的localStoragesessionStorage提供了在浏览器中存储数据的功能,但它们原生仅支持存储字符串类型的键值对。当需要存储对象、数组等复杂数据类型时,就需要对数据进行处理。

实现步骤

基本方法:使用JSON.stringify()和JSON.parse()

将对象转换为字符串进行存储,取出时再将字符串解析为对象。

var testObject = { 'one': 1, 'two': 2, 'three': 3 };

// 存储对象
localStorage.setItem('testObject', JSON.stringify(testObject));

// 读取对象
var retrievedObject = localStorage.getItem('testObject');
console.log('retrievedObject: ', JSON.parse(retrievedObject));

扩展Storage对象的方法

可以通过扩展Storage对象的原型来简化操作。

Storage.prototype.setObject = function(key, value) {
    this.setItem(key, JSON.stringify(value));
};

Storage.prototype.getObject = function(key) {
    var value = this.getItem(key);
    return value && JSON.parse(value);
};

// 使用示例
var obj = { name: 'John' };
localStorage.setObject('user', obj);
var retrievedObj = localStorage.getObject('user');

处理不同数据类型

不同的数据类型在存储和读取时需要不同的处理方式。

// 对象和数组
var obj = { key: "value" };
localStorage.object = JSON.stringify(obj);
obj = JSON.parse(localStorage.object);

// 布尔值
var bool = false;
localStorage.bool = bool;
bool = (localStorage.bool === "true");

// 数字
var num = 42;
localStorage.num = num;
num = +localStorage.num;

// 日期
var date = Date.now();
localStorage.date = date;
date = new Date(parseInt(localStorage.date));

// 正则表达式
var regex = /^No\.[\d]*$/i;
localStorage.regex = regex;
var components = localStorage.regex.match("^/(.*)/([a-z]*)#34;);
regex = new RegExp(components[1], components[2]);

处理私有成员

使用JSON.stringify()无法序列化私有成员,可以通过重写.toString()方法解决。

function MyClass(privateContent, publicContent) {
    var privateMember = privateContent || "defaultPrivateValue";
    this.publicMember = publicContent || "defaultPublicValue";

    this.toString = function() {
        return '{ "private": "' + privateMember + '", "public": "' + this.publicMember + '"}';
    };
}

MyClass.fromString = function(serialisedString) {
    var properties = JSON.parse(serialisedString || "{}");
    return new MyClass(properties.private, properties.public);
};

// 存储
var obj = new MyClass("invisible", "visible");
localStorage.object = obj;

// 读取
obj = MyClass.fromString(localStorage.object);

处理循环引用

JSON.stringify()无法处理循环引用,可使用其第二个参数来解决。

var obj = { id: 1, sub: {} };
obj.sub["circular"] = obj;
localStorage.object = JSON.stringify(obj, function(key, value) {
    if (key == 'circular') {
        return "$ref" + value.id + "#34;;
    } else {
        return value;
    }
});

核心代码

扩展Storage对象处理数组

Storage.prototype.getArray = function(arrayName) {
    var thisArray = [];
    var fetchArrayObject = this.getItem(arrayName);
    if (typeof fetchArrayObject!== 'undefined') {
        if (fetchArrayObject!== null) { thisArray = JSON.parse(fetchArrayObject); }
    }
    return thisArray;
};

Storage.prototype.pushArrayItem = function(arrayName, arrayItem) {
    var existingArray = this.getArray(arrayName);
    existingArray.push(arrayItem);
    this.setItem(arrayName, JSON.stringify(existingArray));
};

Storage.prototype.popArrayItem = function(arrayName) {
    var arrayItem = {};
    var existingArray = this.getArray(arrayName);
    if (existingArray.length > 0) {
        arrayItem = existingArray.pop();
        this.setItem(arrayName, JSON.stringify(existingArray));
    }
    return arrayItem;
};

Storage.prototype.shiftArrayItem = function(arrayName) {
    var arrayItem = {};
    var existingArray = this.getArray(arrayName);
    if (existingArray.length > 0) {
        arrayItem = existingArray.shift();
        this.setItem(arrayName, JSON.stringify(existingArray));
    }
    return arrayItem;
};

Storage.prototype.unshiftArrayItem = function(arrayName, arrayItem) {
    var existingArray = this.getArray(arrayName);
    existingArray.unshift(arrayItem);
    this.setItem(arrayName, JSON.stringify(existingArray));
};

Storage.prototype.deleteArray = function(arrayName) {
    this.removeItem(arrayName);
};

处理循环引用的完整实现

LOCALSTORAGE = (function() {
    "use strict";
    var ignore = [Boolean, Date, Number, RegExp, String];
    function primitive(item) {
        if (typeof item === 'object') {
            if (item === null) { return true; }
            for (var i = 0; i < ignore.length; i++) {
                if (item instanceof ignore[i]) { return true; }
            }
            return false;
        } else {
            return true;
        }
    }
    function infant(value) {
        return Array.isArray(value)? [] : {};
    }
    function decycleIntoForest(object, replacer) {
        if (typeof replacer!== 'function') {
            replacer = function(x) { return x; };
        }
        object = replacer(object);
        if (primitive(object)) return object;
        var objects = [object];
        var forest = [infant(object)];
        var bucket = new WeakMap(); 
        bucket.set(object, 0);    
        function addToBucket(obj) {
            var result = objects.length;
            objects.push(obj);
            bucket.set(obj, result);
            return result;
        }
        function isInBucket(obj) { return bucket.has(obj); }
        function processNode(source, target) {
            Object.keys(source).forEach(function(key) {
                var value = replacer(source[key]);
                if (primitive(value)) {
                    target[key] = { value: value };
                } else {
                    var ptr;
                    if (isInBucket(value)) {
                        ptr = bucket.get(value);
                    } else {
                        ptr = addToBucket(value);
                        var newTree = infant(value);
                        forest.push(newTree);
                        processNode(value, newTree);
                    }
                    target[key] = { pointer: ptr };
                }
            });
        }
        processNode(object, forest[0]);
        return forest;
    }
    function deForestIntoCycle(forest) {
        var objects = [];
        var objectRequested = [];
        var todo = [];
        function processTree(idx) {
            if (idx in objects) return objects[idx];
            if (objectRequested[idx]) return null;
            objectRequested[idx] = true;
            var tree = forest[idx];
            var node = Array.isArray(tree)? [] : {};
            for (var key in tree) {
                var o = tree[key];
                if ('pointer' in o) {
                    var ptr = o.pointer;
                    var value = processTree(ptr);
                    if (value === null) {
                        todo.push({
                            node: node,
                            key: key,
                            idx: ptr
                        });
                    } else {
                        node[key] = value;
                    }
                } else {
                    if ('value' in o) {
                        node[key] = o.value;
                    } else {
                        throw new Error('unexpected');
                    }
                }
            }
            objects[idx] = node;
            return node;
        }
        var result = processTree(0);
        for (var i = 0; i < todo.length; i++) {
            var item = todo[i];
            item.node[item.key] = objects[item.idx];
        }
        return result;
    }
    var console = {
        log: function(x) {
            var the = document.getElementById('the');
            the.textContent = the.textContent + '\n' + x;
        },
        delimiter: function() {
            var the = document.getElementById('the');
            the.textContent = the.textContent +
                '\n*******************************************';
        }
    }
    function logCyclicObjectToConsole(root) {
        var cycleFree = decycleIntoForest(root);
        var shown = cycleFree.map(function(tree, idx) {
            return false;
        });
        var indentIncrement = 4;
        function showItem(nodeSlot, indent, label) {
            var leadingSpaces =' '.repeat(indent);
            var leadingSpacesPlus =' '.repeat(indent + indentIncrement);
            if (shown[nodeSlot]) {
                console.log(leadingSpaces + label + ' ... see above (object #' + nodeSlot + ')');
            } else {
                console.log(leadingSpaces + label + ' object#' + nodeSlot);
                var tree = cycleFree[nodeSlot];
                shown[nodeSlot] = true;
                Object.keys(tree).forEach(function(key) {
                    var entry = tree[key];
                    if ('value' in entry) {
                        console.log(leadingSpacesPlus + key + ": " + entry.value);
                    } else {
                        if ('pointer' in entry) {
                            showItem(entry.pointer, indent + indentIncrement, key);
                        }
                    }
                });
            }
        }
        console.delimiter();
        showItem(0, 0, 'root');
    }
    function stringify(obj) {
        return JSON.stringify(decycleIntoForest(obj));
    }
    function parse(str) {
        return deForestIntoCycle(JSON.parse(str));
    }
    var CYCLICJSON = {
        decycleIntoForest: decycleIntoForest,
        deForestIntoCycle: deForestIntoCycle,
        logCyclicObjectToConsole: logCyclicObjectToConsole,
        stringify: stringify,
        parse: parse
    }
    function setObject(name, object) {
        var str = stringify(object);
        localStorage.setItem(name, str);
    }
    function getObject(name) {
        var str = localStorage.getItem(name);
        if (str === null) return null;
        return parse(str);
    }
    return {
        CYCLICJSON: CYCLICJSON,
        setObject: setObject,
        getObject: getObject
    }
})();

最佳实践

  • 使用抽象库:如jStoragesimpleStoragelocalForage等,这些库提供了更好的兼容性和更多的功能。
  • 对于TypeScript用户,可以使用类型化的包装器来确保类型安全。
export class TypedStorage<T> {
    public removeItem(key: keyof T): void {
        localStorage.removeItem(key);
    }

    public getItem<K extends keyof T>(key: K): T[K] | null {
        const data: string | null = localStorage.getItem(key);
        return JSON.parse(data);
    }

    public setItem<K extends keyof T>(key: K, value: T[K]): void {
        const data: string = JSON.stringify(value);
        localStorage.setItem(key, data);
    }
}

// 使用示例
interface MyStore {
    age: number;
    name: string;
    address: { city: string };
}

const storage: TypedStorage<MyStore> = new TypedStorage<MyStore>();
storage.setItem("address", { city: "Here" });
const address: { city: string } = storage.getItem("address");

常见问题

函数存储问题

不建议存储函数,因为eval()存在安全、优化和调试方面的问题,且函数序列化/反序列化依赖于具体实现。

循环引用问题

JSON.stringify()无法处理循环引用,需要使用额外的方法来解决。

私有成员问题

JSON.stringify()无法序列化私有成员,可通过重写.toString()方法解决。

点击这里复制本文地址 以上内容由朽木教程网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

朽木教程网 © All Rights Reserved.  蜀ICP备2024111239号-8