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

引言

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容