Web编程技术营地
研究、演示、创新

对象的持续化

撰写时间:2025-08-27

修订时间:2025-08-27

概述

有时,有的JavaScript对象较大,在初始化时花费了较长的时间。在开发阶段,因经常需要刷新网页,一个较好的解决方案是将这些初始化比较耗时的对象缓存进sessionStoragelocalStorage,这样,当多次刷新网页时,将会节省不少时间。

sessionStoragelocalStorage在存储数据时,只能存储类型为字符串的数值。若想存储对象,则需要先调用JSON.stringify方法先将对象转换为字符串进行存储,然后再调用JSON.parse方法将存储的字符串转换回对象。

但使用JSON时有一些限制或不足。如,对象不能循环引用,无法将对象的方法进行转换,等等。例如:

let obj = { name: 'Mike', greet() { console.log('Hello'); } }; pc.log(obj); let jsonStr = JSON.stringify(obj); pc.log('%s', jsonStr); let newObj = JSON.parse(jsonStr); pc.log(newObj);

当调用JSON.stringify方法时,所生成的字符串中,已经舍弃了greet方法的信息,再转换回对象,该方法自然也丢失了。

本文为解决类似问题提供了一个参考思路。

总体思路

总体思路是,先将对象的各个方法转换为可持续化的普通属性,以便安全地调用JSON.stringify,以字符串的形式缓存进sessionStorage;之后,从sessionStorage取出字符串,调用JSON.parse转换为对象,为对象动态地添加各个方法。最后,调用对象的各个方法。

将各个方法转换为普通属性

let obj = { name: 'Mike', age: 25, greet() { pc.log(`Hello, I'am ${this.name}.`); }, say() { pc.log('Hi'); } }; function serialize(obj) { let shallowClone = Object.assign({}, obj); let funcs = []; let re = /(\w+)\(\) {(.+)}/s; for (let propName in shallowClone) { if (typeof shallowClone[propName] === 'function') { let src = shallowClone[propName].toString(); let funcName, funcBody; src.replace(re, (match, p1, p2) => { funcName = p1; funcBody = p2; }); funcs.push({name: funcName, body: funcBody}); delete shallowClone[propName]; } } shallowClone.funcs = funcs; let deepClone = structuredClone(shallowClone); return deepClone; } let serializedObj = serialize(obj); pc.log(serializedObj); pc.assert(obj.greet, obj.say);

函数参数objname, age2个属性,这些属性不需要动它们。有greet, say2个方法,需要转换为普通属性后,再删除这些方法。

serialize函数中,先调用Object.assign进行一个浅复制 (shallow copy),这种复制包括可枚举且是自身的属性,包括函数。此时,对象属性若持有引用,则浅复制的属性值也为引用而非实际的对象值。但,如上所述,我们不会动这些可能包含引用的属性。

对于shallowClone的每个方法,使用正则表达式分离出它们的名称及函数体的内容,然后以{name, body}的形式添加进一个funcs数组,然后,逐一删除加工完毕的shallowClone的方法。由于shallowClone的方法并非引用的关系,因此在shallowClone上面删除其方法,不会影响函数参数obj

接着将数组funcs添附为shallowClone的属性。这样,shallowClone就以普通属性的方式,保存了obj相关方法的信息。现在,可以进行深复制了。

最后,调用structuredClone函数,将shallowClone深克隆 (deep clone)为一个完全独立的对象,并返回此对象。由于shallowClone此时已无任何方法,因此不会出错。

在主流程中,可以看到serializedObj的各个对象属性。最后,调用pc.assert方法确保原来的对象obj中的各个方法不会被我们误删。

保存进sessionStorage

// continue with previous codes // ... function saveInSessionStorage(obj, key) { let jsonStr = JSON.stringify(obj); pc.log('%s', jsonStr); sessionStorage.setItem(key, jsonStr); } const KEY = 'myObj'; saveInSessionStorage(serializedObj, KEY);

由于serializedObj已无方法,因此在saveInSessionStorage函数中,可安全地调用JSON.stringify方法以转换为字符串,再存储至sessionStorage中。

现在,在浏览器的开发者工具的储存空间的会话储存空间中,应可看到一个密钥名为myObj,其值是上面所打印出来的字符串值。

从sessionStorage中取出

// continue with previous codes // ... function loadFromSessionStorage(key) { let jsonStr = sessionStorage.getItem(key); let obj = JSON.parse(jsonStr); return obj; } const KEY = 'myObj'; let loadedObj = loadFromSessionStorage(KEY); pc.log(loadedObj);

先从sessionStorage取出其字符串值,再调用JSON.parse方法转换为一个对象实例。该对象只有原来各方法的信息,但无任何实际方法。

动态添加方法

// continue with previous codes // ... function deSerialize(obj) { let localObj = structuredClone(obj); localObj.funcs.forEach(wrapper => { localObj[wrapper.name] = new Function(wrapper.body); }); delete localObj.funcs; return localObj; } let deSerializedObj = deSerialize(loadedObj); pc.log(deSerializedObj); deSerializedObj.greet(); deSerializedObj.say();

deSerialize函数中,先深克隆一个对象,根据其funcs属性值的信息,调用Function构造函数,动态生成各个方法,接着删除该对象的funcs属性后返回该对象。

最后,调用此对象的greetsay方法以输出信息。

至此,我们成功将一个带有多个方法的对象保存进sessionStorage,取出后再调用其方法。

参考资源

Specifications

  1. HTML Living Standard: Safe passing of structured data

MDN

  1. JSON.stringify()