共计 3887 个字符,预计需要花费 10 分钟才能阅读完成。
需求:
几乎所有的 web 应用都需要保存数据一些到本地,那么我们就来做一个数据储存器吧。
详细需求:
当本地储存有数据时,取用本地的数据,没有时使用默认的数据
判断本地的数据是否过时,如果过时则不使用
默认使用 localStorage,但支持使用其它储存方式,并且要支持多方储存,多方读取
抽象出对象
根据需求里面的关键字,我们抽象出三个对象:数据访问、数据、储存器
数据储存管理器负责管理数据,对外暴露接口
数据对象负责对数据的操作
储存器负责保持数据,读取数据
储存器对象
DataStorageManagerBase 暴露两个接口 save() 和 load(), 模拟抽象类,告诉子类一定要实现这两个方法。
下面的例子用 LocalStorage 实现了一个子类,当然你也可以用 cookie 或其它方式实现。
为什么要把 LocalStorage 这些储存器进行二次封装呢?直接用不就可以了吗?
因为各种储存器等 api 都是不一样等,我们二次封装后可以确保无论什么储存器对外暴露的接口都是 save() 和 load()。
/* 模拟数据储存器抽象类,其实这个类不要也可以 */ | |
class DataStorageManagerBase {static getIns() { | |
/* 储存器在整个应用的生命周期里应该只有一个实例 */ | |
if (!this._ins) this._ins = new this(); | |
return this._ins; | |
} | |
constructor() {this.name = null;} | |
save(name/* string */, data/* any */) {throw '"'+ this.constructor.name +"' 类没有 save() 方法 "; | |
} | |
load(name/* string */) {throw '"'+ this.constructor.name +"' 类没有 load() 方法 "; | |
} | |
} | |
class LocalStorageManager extends DataStorageManagerBase {static getIns() { | |
/* 静态方法不能继承 */ | |
if (!this._ins) this._ins = new this(); | |
return this._ins; | |
} | |
constructor() {super(); | |
this.name = 'localStorage'; | |
} | |
save(name/* string */, data/* any */) {console.log(name,data) | |
if (!window.localStorage) return this;// 判断这个储存器可用不可用,你也可以在这里抛出错误 | |
window.localStorage[name] = JSON.stringify(data); | |
return this; | |
} | |
load(name/* string */) { | |
// 如果储存器不可用,返回 false | |
if (!window.localStorage) return false; | |
// 如果没有这个数据,返回 false | |
if (!window.localStorage[name]) return false; | |
let dataLoaded = JSON.parse(window.localStorage[name]); | |
return dataLoaded; | |
} | |
} |
数据对象
对数据的操作:保存、读取、判断版本等
class GlobalData {static addStorage(storage/* DataStorageManagerBase */) { | |
/* 动态添加储存器 */ | |
this._storages.push();} | |
static getStorages() {return this._storages;} | |
constructor(name, data, version) { | |
this.name = name; | |
this.data = data; | |
this.version = version || 1; | |
this._loadData(); | |
// 初始化的该对象的时候,读取 localStorage 里的数据,看有没有已储存的数据,有就用该数据 | |
} | |
getData() {return this._copy(this.data); | |
} | |
setData(data, notSave) {this.data = this._copy(data); | |
if (!!notSave) return this; | |
let dataToSave = { | |
name: this.name, | |
version: this.version, | |
data: this.data | |
}; | |
let storages = GlobalData.getStorages(); | |
for (let i = 0, l = storages.length; i < l; i++) { | |
/* 轮询所有储存器,把数据保存在这些储存器中 */ | |
storages[i].save(this.name,dataToSave); | |
} | |
return this; | |
} | |
_copy(data) { | |
/* 深拷贝 */ | |
if (typeof data != "object") return data; | |
return JSON.parse(JSON.stringify(data)); | |
} | |
_loadData() {let storages = GlobalData.getStorages(); | |
for (let i = 0, l = storages.length; i < l; i++) { | |
/* 轮询所有储存器,依次获取数据 */ | |
const dataLoaded = storages[i].load(this.name); | |
if(!!dataLoaded) {this._updateData(dataLoaded); | |
return; | |
} | |
} | |
} | |
_updateData(dataLoaded) {if (dataLoaded.version < this.version) return this; | |
this.data = dataLoaded.data; | |
return this; | |
} | |
} | |
GlobalData._storages = [LocalStorageManager.getIns()];// 默认添加 LocalStorageManager 储存器 |
数据访问对象
对数据对象管理, 对外暴露三个接口 getData(),setData(),config(), 用户通过这三个接口使用这个模块
class GlobalDataDao {static getIns() {if (!this._ins) this._ins = new this(); | |
return this._ins; | |
} | |
constructor() { | |
this.GlobalDataClass = GlobalData; | |
this._dataList = [];} | |
getData(name/* string */) {let dataIns = this.getDataIns(name); | |
if (!!dataIns) {return dataIns.getData(); | |
} else {return null;} | |
} | |
setData(name/* string */, data/* any */, notSave = false/* ?boolean */) {let dataIns = this.getDataIns(name); | |
dataIns.setData(data, notSave); | |
return this; | |
} | |
config(configs/* Array */) { | |
/* 初始化数据 | |
interface Config { | |
name: string; | |
data;any; | |
version?: number; | |
} | |
*/ | |
for (let i in configs) {let de = configs[i]; | |
if (!!this.getDataIns(de.name)) { | |
/* 如果数据名重复,抛出错误 */ | |
throw new Error('data name"' + de.name + '"is exist'); | |
}; | |
let dataIns = new GlobalData(de.name, de.data, de.version); | |
this._dataList.push(dataIns); | |
} | |
return this; | |
} | |
getDataIns(name/* string */) {for (let i in this._dataList) {if (this._dataList[i].name === name) {return this._dataList[i]; | |
} | |
} | |
return false; | |
} | |
} |
使用
/* 用法 */ | |
let globalDataManeger = GlobalDataDao.getIns(); | |
let configs = [ | |
{ | |
name: 'languageUsing', | |
version: 1, | |
data: { | |
name: '简体中文', | |
value: 'zh-cn' | |
} | |
}, { | |
name: 'userPreference', | |
version: 1, | |
data: { | |
theme: 'blue', | |
menu: 'side_bar' | |
} | |
} | |
]; | |
globalDataManeger.config(configs); | |
console.log(globalDataManeger); | |
let languageUsing = globalDataManeger.getData('languageUsing'); | |
console.log(languageUsing); | |
languageUsing.name = 'English'; | |
languageUsing.value = 'en'; | |
globalDataManeger.setData('languageUsing', languageUsing); |
正文完
星哥玩云-微信公众号
