对象的持续化
撰写时间:2025-08-27
修订时间:2025-08-27
概述
有时,有的JavaScript对象较大,在初始化时花费了较长的时间。在开发阶段,因经常需要刷新网页,一个较好的解决方案是将这些初始化比较耗时的对象缓存进sessionStorage或localStorage,这样,当多次刷新网页时,将会节省不少时间。
sessionStorage及localStorage在存储数据时,只能存储类型为字符串的数值。若想存储对象,则需要先调用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);
函数参数obj有name, age共2个属性,这些属性不需要动它们。有greet, say共2个方法,需要转换为普通属性后,再删除这些方法。
在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属性后返回该对象。
最后,调用此对象的greet及say方法以输出信息。
至此,我们成功将一个带有多个方法的对象保存进sessionStorage,取出后再调用其方法。