原生JS中如何实现模型对象Setter自动更新IndexedDB?
问题描述
我想要创建一个对象,使其在任何属性被设置时自动更新对应的IndexedDB数据。
现有代码可正常运行,但每次从IndexedDB获取数据后,都需要通过以下方式实例化对象:
new ResultsModel(indexedDbData._scores,indexedDbData._overallScore,indexedDbData.id)
这看起来是一种“反模式”,因为我直接调用了本应属于“私有”的变量,尽管目前它只是数据。
请问这是否存在问题?我也不确定应该使用Setter和Getter,还是直接创建update函数?实现该需求的合适模式是什么?
现有代码
export class ResultsModel{ constructor(scores,overallScore, id){ this._scores = scores; this._overallScore = overallScore; this.id = id; } async init(){ sessionStorage.setItem("lastResults", JSON.stringify(this)); await this.save(); } async save(){ let req = await db.transaction("moodResults","readwrite").objectStore("moodResults").put(this); req.onsuccess = function(event) {console.log("saved")} req.onerror = function(event) {console.log("error")} } get scores(){ return this._scores; } get overallScore(){ return this._overallScore; } set scores(newValue){ this._scores = newValue; } set overallScore(newValue){ this._overallScore = newValue; if(this.overallScore === undefined){return} return (async () =>{ let objectStore = await db.transaction("moodResults","readwrite").objectStore("moodResults"); const req = await objectStore.get(parseInt(this.id)); req.onsuccess = async () =>{ if(req.result === undefined){ throw new Error("Object with specified key not found"); } const data = req.result; data._overallScore = newValue; const updateOverallScore = objectStore.put(data) updateOverallScore.onsuccess = () =>{ console.log("update worked") } updateOverallScore.onerror = () =>{ console.log("update didn't worked") } } req.onerror = () =>{ console.log("getting object didn't work") } })() } }
解答
1. 直接访问“私有”变量的问题
你现在直接用indexedDbData._scores这类下划线前缀的变量确实存在问题:
- 下划线前缀是JS里约定俗成的私有标识,直接暴露访问会破坏类的封装性,后续如果类内部对这些变量的存储逻辑修改(比如换变量名、增加预处理逻辑),所有外部实例化代码都要同步修改,维护成本极高。
- 从IndexedDB取出的对象本身就和
ResultsModel的结构一致,完全没必要拆成零散参数再传入构造函数,属于冗余操作。
2. Setter/Getter vs Update函数的选择
结合你“属性变更自动更新DB”的核心需求,Setter是更合适的选择:
- Setter能在属性赋值时自动触发更新逻辑,代码更直观,比如
model.overallScore = 90就能自动同步DB,不用手动调用额外的更新函数,完全符合“自动更新”的初衷。 - Update函数更适合批量更新或复杂业务场景,但需要手动调用,无法实现“属性变更即同步”的自动化效果。
不过你现有Setter的实现存在明显冗余:scores的Setter没有做DB更新,overallScore的Setter又重复编写了DB查询+更新逻辑,完全可以复用已有的save()方法来简化代码。
3. 合适的实现模式:封装初始化+统一Setter触发更新
推荐用工厂方法处理从IndexedDB数据初始化模型的逻辑,同时统一所有Setter的更新逻辑,复用save()方法,避免重复代码。优化后的代码示例:
export class ResultsModel { // 用#实现真正的私有字段(ES6+支持),比下划线前缀的伪私有更严谨 #scores; #overallScore; id; // 构造函数改为内部初始化,外部通过工厂方法创建实例 constructor(data) { // 兼容旧数据结构,同时隐藏私有字段的细节 this.#scores = data._scores || data.scores; this.#overallScore = data._overallScore || data.overallScore; this.id = data.id; } // 工厂方法:从IndexedDB数据直接创建模型实例,对外隐藏构造细节 static fromDBData(dbData) { return new ResultsModel(dbData); } async init() { // 自定义序列化逻辑,避免私有字段无法被JSON识别 sessionStorage.setItem("lastResults", JSON.stringify(this.toJSON())); await this.save(); } async save() { try { const transaction = db.transaction("moodResults", "readwrite"); const objectStore = transaction.objectStore("moodResults"); // 存入序列化后的对象,保证数据结构正确 await objectStore.put(this.toJSON()); console.log("保存/更新成功"); } catch (err) { console.error("保存/更新失败:", err); } } // 自定义序列化逻辑,确保私有字段能被正确序列化 toJSON() { return { id: this.id, _scores: this.#scores, _overallScore: this.#overallScore }; } get scores() { return this.#scores; } set scores(newValue) { this.#scores = newValue; // 赋值后自动触发DB更新,捕获避免未处理的异步错误 this.save().catch(err => console.error("更新scores失败:", err)); } get overallScore() { return this.#overallScore; } set overallScore(newValue) { if (newValue === undefined) return; this.#overallScore = newValue; // 复用save方法,不用重复编写DB操作逻辑 this.save().catch(err => console.error("更新overallScore失败:", err)); } }
优化点说明:
- 工厂方法
fromDBData:外部从IndexedDB取数据后,直接调用ResultsModel.fromDBData(indexedDbData)即可创建实例,不用再手动拆分私有变量,彻底解决之前的反模式问题。 - 真正的私有字段:用
#scores代替_scores,ES6+原生支持的私有字段,外部无法直接访问,彻底保证类的封装性。 - 统一更新逻辑:所有Setter赋值后都调用
save()方法,避免重复编写DB操作代码,减少冗余。 - 修复异步逻辑问题:原代码中Setter的异步逻辑处理不严谨,现在直接调用
save()并捕获错误,避免出现未处理的Promise异常。 - 自定义
toJSON:解决私有字段无法被JSON.stringify序列化的问题,保证存入IndexedDB和sessionStorage的数据结构正确。
使用方式
从IndexedDB取数据后创建实例:
const model = ResultsModel.fromDBData(indexedDbData);
更新属性时自动同步DB:
model.scores = [85, 90, 95]; model.overallScore = 90;
内容的提问来源于stack exchange,提问作者Trying Vlad
相关产品推荐
相关产品推荐

