JS设计模式(三) 数据访问对象模式

作者: kMacro | 来源:发表于2017-07-31 01:38 被阅读216次

    引言

    HTML5 提供了两种在客户端存储数据的新方法:localStorage、sessionStorage,他们是Web Storage API 提供的两种存储机制,区别在于前者属于永久性存储,而后者是局限于当前窗口的数据传递,存储在其中的数据会在当前会话结束时被删除。localStorage、sessionStorage的具体内容在这里就不多做介绍了,我们主要探讨一下在实际开发中怎样合理使用他们。

    问题

    大部分网站会将一些数据(如:用户Token)存储在前端,用来实现页面间的传值,对于一些大型Web应用来说,其存储的数据可能会非常多,数据的管理会变得复杂,并且一个大型项目是由多位程序员共同开发的,这时就会遇到一个问题:怎样确保自己的数据不会覆盖掉其他人的呢?因为在一个页面中大家都是使用同一个WebStorage对象,总不能把大家使用过的Key记录下来吧。这时候就可以使用数据访问对象模式来解决了。

    数据访问对象模式(DAO)

    数据访问对象模式就是对数据源的访问与存储进行封装,提供一个数据访问对象类负责对存储的数据进行管理和操作,规范数据存储格式,类似于后台的DAO层。
    由于WebStorage采用Key-Value的方式存取数据,而且只能存字符串(任何类型存储的时候都会被转为字符串,读取的时候需要进行类型转换),所以我们可以对Key的格式进行规范,比如模块名+Key,开发人员+Key等,还可以在值中添加一段前缀用来描述数据,如添加数据过期日期的时间戳,用来管理数据的生命周期。具体格式项目组可以自己定义,主要是便于管理,防止出现冲突,约定好规范后就可以开始定义数据访问对象了。

    下面以localStorage为例,介绍一下数据访问对象类的定义和使用。

    代码示例

    DAO类基本结构

    数据访问对象类的基本结构如下,我们给键值添加了一段前缀用来避免键值冲突,并且在值中加入数据过期时间戳以及分隔符,获取值的时候再进行判断是否过期,这样可以更灵活地管理存储数据的生命周期。这里还用到了回调的方式,方便获取数据访问过程的具体结果,以及在必要时执行相关操作。

    /**
     * LocalStorage数据访问类
     * @param {string} prefix Key前缀
     * @param {string} timeSplit 时间戳与存储数据之间的分割符
     */
    var Dao = function (prefix, timeSplit) {
        this.prefix = prefix;
        this.timeSplit = timeSplit || '|-|';
    }
    // LocalStorage数据访问类原型方法
    Dao.prototype = {
        // 操作状态
        status: {
            SUCCESS: 0,     // 成功
            FAILURE: 1,     // 失败
            OVERFLOW: 2,    // 溢出
            TIMEOUT: 3      // 过期
        },
        // 本地存储对象
        storage: localStorage || window.localStorage,
        // 获取带前缀的真实键值
        getKey: function (key) {
            return this.prefix + key;
        },
        // 添加(修改)数据
        set: function (key, value, callback, time) {
           ...
        },
        // 获取数据
        get: function (key, callback) {
            ...
        },
        // 删除数据
        remove: function (key, callback) {
            ...
        }
    }
    

    添加(修改)数据

    /**
        * 添加(修改)数据
        * @param key 数据字段标识
        * @param value 数据值
        * @param callback 回调函数
        * @param time 过期时间
        */
        set: function (key, value, callback, time) {
            // 默认为成功状态
            var status = this.status.SUCCESS,
                key = this.getKey(key);
            try {
                // 获取过期时间戳
                time = new Date(time).getTime() || time.getTime();
            } catch (e) {
                // 未设置过期时间时默认为一个月
                time = new Date().getTime() + 1000 * 60 * 60 * 24 * 30;
            }
            try {
                // 向本地存储中添加(修改)数据
                this.storage.setItem(key, time + this.timeSplit + value);
            } catch (e) {
                // 发生溢出
                status = this.status.OVERFLOW;
            }
            // 执行回调并传入参数
            callback && callback.call(this, status, key, value);
        }
    

    获取数据

    /**
        * 获取数据
        * @param key 数据字段标识
        * @param callback 回调函数
        */
        get: function (key, callback) {
            var key = this.getKey(key),
                status = this.status.SUCCESS,    // 获取数据状态
                value = null;    // 获取数据值
    
            try {
                // 从本地存储获取数据
                value = this.storage.getItem(key);
            } catch (e) {
                // 获取数据失败
                status = this.status.FAILURE;
                value = null;
            }
    
            // 如果成功获取数据
            if (status !== this.status.FAILURE) {
                var index = value.indexOf(this.timeSplit),
                    timeSplitLen = this.timeSplit.length,
                    // 获取时间戳
                    time = value.slice(0, index);
                // 判断数据是否未过期
                if (new Date(1*time).getTime() > new Date().getTime() || time == 0) {
                    // 获取数据值
                    value = value.slice(index + timeSplitLen);
                } else {
                    // 数据已过期,删除数据
                    value = null;
                    status = this.status.TIMEOUT;
                    this.remove(key);
                }
            }
    
            // 执行回调
            callback && callback.call(this, status, value);
            // 返回结果值
            return value;
        }
    

    删除数据

    /**
        * 删除数据
        * @param key 数据字段标识
        * @param callback 回调函数
        */
        remove: function (key, callback) {
            // 设置默认状态为失败
            var status = this.status.FAILURE,
                key = this.getKey(key),
                value = null;
            try {
                // 获取数据值
                value = this.storage.getItem(key);
            } catch (e) {
                // 数据不存在,不采取操作
            }
            // 如果数据存在
            if (value) {
                try {
                    // 删除数据
                    this.storage.removeItem(key);
                    status = this.status.SUCCESS;
                } catch (e) {
                    // 数据删除失败,不采取操作
                }
            }
            // 执行回调并传入参数,删除成功则传入被删除的数据值
            callback && callback.call(this, status, status > 0 ? null : value.slice(value.indexOf(this.timeSplit) + this.timeSplit.length));
        }
    

    用法

    var dao = new Dao('myModule_');
    // 添加/修改数据
    dao.set('token', 'abc', function () { console.log(arguments); });
    // 获取数据
    var value = dao.get('token', function () { console.log(arguments); });
    console.log(value);
    // 删除数据
    dao.remove('token', function () { console.log(arguments); });
    

    写在最后

    其实数据访问对象模式更适合与服务器端的数据库操作,比如在nodejs中操作MongoDB,通过对数据库增删改查操作的封装,可以方便我们对前端存储的管理,不必为操作数据库感到烦恼,DAO已经为我们提供了便捷统一的接口,这样在团队开发中就不用担心影响到其他人的数据了。

    本文为作者kMacro原创,转载请注明来源:http://www.jianshu.com/p/ffdaa99dbde8

    相关文章

      网友评论

        本文标题:JS设计模式(三) 数据访问对象模式

        本文链接:https://www.haomeiwen.com/subject/ayrklxtx.html