logo
Published on

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

Authors
  • Name
    Twitter

在日常开发中,我们可能会遇到需要在HTML5的localStorage或sessionStorage中存储复杂对象的情况。有些对象包含循环引用,这使得我们无法使用普通的JSON.stringifyJSON.parse进行存储和恢复。本文将介绍如何实现这一需求,并提供一个涵盖循环引用处理的完整实现方案。

循环引用对象示例

首先,让我们创建一个包含循环引用的对象:

var obj = {
    L: {
        L: { v: 'lorem' },
        R: { v: 'ipsum' }
    },
    R: {
        L: { v: 'dolor' },
        R: {
            L: { v: 'sit' },
            R: { v: 'amet' }
        }
    }
};
obj.R.L.uncle = obj.L;
obj.R.R.uncle = obj.L;
obj.R.R.L.uncle = obj.R.L;
obj.R.R.R.uncle = obj.R.L;
obj.L.L.uncle = obj.R;
obj.L.R.uncle = obj.R;

在这种情况下,我们不能直接使用JSON.stringify对其进行序列化,因为循环引用会导致错误。

解决方案:使用定制的localStorage

我们将在 LOCALSTORAGE.CYCLICJSON 中使用 stringifyparse 函数。这些函数专门处理循环引用对象。以下是实现代码:

var 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;
    }

    function stringify(obj) {
        return JSON.stringify(decycleIntoForest(obj));
    }

    function parse(str) {
        return deForestIntoCycle(JSON.parse(str));
    }

    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: {
            decycleIntoForest: decycleIntoForest,
            deForestIntoCycle: deForestIntoCycle,
            stringify: stringify,
            parse: parse
        },
        setObject: setObject,
        getObject: getObject
    };
})();

使用示例

我们可以测试上述方法来确保其正确功能:

LOCALSTORAGE.setObject('latinUncles', obj);
var recovered = LOCALSTORAGE.getObject('latinUncles');

// 验证恢复的对象和原对象是否等效
console.log([
    obj.L.L.v === recovered.L.L.v,
    obj.L.R.v === recovered.L.R.v,
    obj.R.L.v === recovered.R.L.v,
    obj.R.R.L.v === recovered.R.R.L.v,
    obj.R.R.R.v === recovered.R.R.R.v,
    obj.R.L.uncle === obj.L,
    obj.R.R.uncle === obj.L,
    obj.R.R.L.uncle === obj.R.L,
    obj.R.R.R.uncle === obj.R.L,
    obj.L.L.uncle === obj.R,
    obj.L.R.uncle === obj.R,
    recovered.R.L.uncle === recovered.L,
    recovered.R.R.uncle === recovered.L,
    recovered.R.R.L.uncle === recovered.R.L,
    recovered.R.R.R.uncle === recovered.R.L,
    recovered.L.L.uncle === recovered.R,
    recovered.L.R.uncle === recovered.R
]);

通过上述代码,我们可以存储和恢复包含循环引用的复杂对象,并确保恢复后的对象保持其原有的关系和数据。

总结

在这篇文章中,我们讨论了如何在HTML5的localStorage和sessionStorage中存储包含循环引用的复杂对象。通过使用自定义的 LOCALSTORAGE.CYCLICJSON 方法,我们避免了 JSON.stringifyJSON.parse 的局限性。希望这些内容对你在实际项目中有所帮助。

英文学术名称:how-to-store-objects-in-html5-localstorage-sessionstorage