You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

原生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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 06:35:50