在之前的博客中,我们使用过工厂、代理模式来封装原生的缓存方法,这一篇我们将缓存方法的细节处理优化一下,来提高项目质量
class Storage {
constructor(props = {}) { // 根据类型跟缓存时间,初始化缓存方法
const { type = 'local', time = 5000, cacheSize = 2.5 } = props
this.type = type
this.time = time
this.cacheSize = cacheSize * 1024 * 1024
this.storageType = {
local: 'localStorage',
session: 'sessionStorage',
cookie: 'cookie',
indexDb: 'indexDb',
normal: 'normal'
}
}
baseSetItem(key, value) { // 接管原生新增方法
return {
local() { window[this.storageType[this.type]].setItem(key, value) },
session() { window[this.storageType[this.type]].setItem(key, value) },
cookie() { },
indexDb() { },
normal() {
if (!window.baseStorage) {
window.baseStorage = {}
} else {
window.baseStorage[key] = value
}
},
}
}
baseRemoveItem(key) { // 接管原生删除方法
return {
local() { window[this.storageType[this.type]].removeItem(key) },
session() { window[this.storageType[this.type]].removeItem(key) },
cookie() { },
indexDb() { },
normal() {
delete window.baseStorage[key]
},
}
}
setItem(key, value) { // 代理原生缓存方法,添加缓存时间
this.baseSetItem(key, JSON.stringify({
value,
time: new Date().getTime()
}))[this.type].apply(this)
}
getItem(key) { // 代理原生获取缓存方法,根据缓存时间,判断数据是否过期
try {
const { time, value } = JSON.parse(window[this.storageType[this.type]].getItem(key));
const now = new Date().getTime()
if (now > time + this.time) {
this.baseRemoveItem(key, reValue)[this.type].apply(this)
return null
} else {
return value
}
} catch (e) {
return null
}
}
}
const local = new Storage({ type: 'local', time: 30000 }) // 初始化localStorage,添加5分钟缓存时效
const session = new Storage({ type: 'session', time: 30000 }) // 初始化sessionStorage,添加5分钟缓存时效
local.setItem(1, 2)
local.getItem(1)
如上,我们将原生缓存方法通过工厂、代理、策略封装完毕,统一了所有缓存的读取方法,使得业务侧的调用更加简便(由于篇幅原因,此处的 cookie 跟 indexDb 并未添加对应的方法,后期会在 demo 里面进行补充)
这个很简单,就是将 null、 undefined 这种一般没意义的过滤掉(可根据业务自行判断,有些项目可能有意义),并且操作缓存是一种并不安全的操作,可以包一层 try/catch,安全第一。
setItem(key, value) { // 代理原生缓存方法,添加缓存时间
if (!value) return
try {
this.baseSetItem(key, JSON.stringify({
value,
time: new Date().getTime()
}))[this.type]()
} catch (error) {
console.log(error)
}
}
localStorage 中一般浏览器支持的是5M大小,在不同内核的浏览器中 localStorage 会有所不同,所以我们在使用缓存的时候要注意不能超过最大缓存。
getCacheSize() {
let storage = window[this.storageType[this.type]];
if (storage !== "") {
let size = 0;
for (let item in storage) {
if (storage.hasOwnProperty(item)) {
size += storage.getItem(item).length;
}
}
return size
}
return false
}
如上方法,是常见获取本地缓存 size 的方法,当我们在初始化 Storage 的时候,可以预设一个最大 size 进去,方便我们在超过最大 size 的时候进行其他操作。一般来说,避免项目中其他地方也在操作 stroage 以及兼容大部分浏览器,我们预设缓存设置在 2.5M 即可。
constructor(props = {}) { // 根据类型跟缓存时间,初始化缓存方法
const { type = 'local', time = 5000, cacheSize = 2.5 } = props
this.type = type
this.time = time
this.cacheSize = cacheSize * 1024 * 1024
this.storageType = {
local: 'localStorage',
session: 'sessionStorage',
cookie: 'cookie',
indexDb: 'indexDb',
normal: 'normal'
}
this.initEstimate() // 初始化已用缓存
}
initEstimate() {
if (navigator && navigator.storage) {
navigator.storage.estimate().then(estimate => {
this.usage = estimate.usage
this.quota = estimate.quota
console.log(this.usage, this.quota)
});
} else {
this.usage = this.getCacheSize(this.type)
}
console.log(this.usage)
}
上述初始化获取已用缓存大小的方法中有个 navigator.storage.estimate 方法,此方法只支持部分浏览器以及只能在 https 协议下使用,是获取本地缓存使用空间跟最大值的 web api。具体使用可以参考 StorageEstimate。本文暂不对此方法进行额外拓展,后续会在 demo 里面完善。
当我们的数据不得不进行缓存且数据量超过我们预设的最大缓存大小的时候,可以有如下两种方案来解决:
作为 demo, 我们将采取第一种比较暴力点的方法来解决这个问题
getCacheSort() { // 获取本地缓存数据且按照时间排序
let storage = window[this.storageType[this.type]];
if (storage !== "") {
let storageList = [];
for (let item in storage) {
if (storage.hasOwnProperty(item)) {
storageList.push({
key: item,
value: storage.getItem(item)
})
}
}
return storageList.sort((a, b) => {
return JSON.parse(a.value).time - JSON.parse(b.value).time
})
}
return false
}
judgeMemory(value) { // 判断缓存大小,当存入数据缓存大小 + 已使用缓存大小超过预设最大值的时候,循环删除数据,直到能够存入新的数据为止
if (this.getSize(value) + this.usage > this.cacheSize) {
const storageList = this.getCacheSort()
for (let { key, value } of storageList) {
if (this.getSize(value) + this.usage < this.cacheSize) break
this.usage = this.usage - this.getSize(value)
this.baseRemoveItem(key)[this.type].apply(this)
}
} else {
this.usage = this.getSize(value) + this.usage
}
}
setItem(key, value) { // 代理原生缓存方法,添加缓存时间
try {
if (!value) return
const reValue = JSON.stringify({
value,
time: new Date().getTime()
})
this.judgeMemory(reValue) // 存入数据之前先调用内存判断方法
this.baseSetItem(key, reValue)[this.type].apply(this)
} catch (error) {
console.log(error)
}
}
由于我们存入的内容中还加入了时间戳,所以会额外消耗部分内存空间,实际项目中不追求极限的话,可以忽略不计。第二种方案还需要计算调用次数,额外消耗的缓存空间也会更多,可以在实际项目针对性选择方案。当然一般来说,没几个项目会需要这么细致的操作。
上述封装的 storage 并未考虑性能,有同学想直接引入项目使用也行,如果性能有问题,可以再针对性的优化一下
完整的 demo 地址:项目实战 demo,喜欢的朋友可以 star 一下,后续会根据设计模式博文的推出,逐步的将此项目继续拓展出来。
如对文章内容以及实例有任何疑问、见解可添加微信 沟通。
本文使用 mdnice 排版