第 104 题:模拟 localStorage 时如何实现过期时间功能
yygmind opened this issue · 23 comments
// 模拟实现一个 localStorage
const localStorage = (function(){
let store = {};
return {
getItem(key){
if(store[key] && store[key+'time']){
const date = new Date().valueOf();
if(date>store[key+'time']){ // 过期了
this.removeItem(key);
return '已经过期了';
}
}
return store[key] || null;
},
setItem(key,value,time){
store[key] = value.toString();
if(time)store[key+'time'] = time; // 设置过期时间
},
removeItem(key){
delete store[key]
},
clear(){
store = {};
}
}
})()
const localStorageMock = (function() {
let store = {}
return {
getItem: function(key) { return store[key] || null },
setItem: function(key, value, time) { // time 是毫秒级别,过时时间必须大于0ms
time = Number(time)?time:0;
store[key] = value.toString()
if(time>0){this.timeOut(key,time)}
},
timeOut:function(key,time){
let that = this;
let timer = setTimeout(function(){ that.removeItem(key);clearTimeout(timer);},time)
},
removeItem: function(key) { delete store[key] },
clear: function() { store = {} },
}
})()
Object.defineProperty(window, 'localStorage2', {
value: localStorageMock
})
localStorage2.setItem('test',"test",3000)
console.log(localStorage2.getItem("test")) //test
setTimeout(function(){
console.log(localStorage2.getItem("test")) //null
},4000);
定时器没有清除呢
用 cookie 模拟 localStorage
参考 https://developer.mozilla.org/zh-CN/docs/Web/API/Window/localStorage
if (!window.localStorage) {
window.localStorage = {
getItem: function (sKey) {
if (!sKey || !this.hasOwnProperty(sKey)) { return null; }
return unescape(document.cookie.replace(new RegExp("(?:^|.*;\\s*)" + escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*((?:[^;](?!;))*[^;]?).*"), "$1"));
},
key: function (nKeyId) {
return unescape(document.cookie.replace(/\s*\=(?:.(?!;))*$/, "").split(/\s*\=(?:[^;](?!;))*[^;]?;\s*/)[nKeyId]);
},
setItem: function (sKey, sValue) {
if(!sKey) { return; }
document.cookie = escape(sKey) + "=" + escape(sValue) + "; expires=Tue, 19 Jan 2038 03:14:07 GMT; path=/";
this.length = document.cookie.match(/\=/g).length;
},
length: 0,
removeItem: function (sKey) {
if (!sKey || !this.hasOwnProperty(sKey)) { return; }
document.cookie = escape(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";
this.length--;
},
hasOwnProperty: function (sKey) {
return (new RegExp("(?:^|;\\s*)" + escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
}
};
window.localStorage.length = (document.cookie.match(/\=/g) || window.localStorage).length;
}
扩展 localStorage 支持 expires
(function () {
var getItem = localStorage.getItem.bind(localStorage)
var setItem = localStorage.setItem.bind(localStorage)
var removeItem = localStorage.removeItem.bind(localStorage)
localStorage.getItem = function (keyName) {
var expires = getItem(keyName + '_expires')
if (expires && new Date() > new Date(Number(expires))) {
removeItem(keyName)
removeItem(keyName + '_expires')
}
return getItem(keyName)
}
localStorage.setItem = function (keyName, keyValue, expires) {
if (typeof expires !== 'undefined') {
var expiresDate = new Date(expires).valueOf()
setItem(keyName + '_expires', expiresDate)
}
return setItem(keyName, keyValue)
}
})()
使用
localStorage.setItem('key', 'value', new Date() + 10000) // 10 秒钟后过期
localStorage.getItem('key')
搞不懂啊
localstorage本身没有时间过期的功能,要是自己封装一个简单的localstorage功能的话,可以使用定时器去实现过期时间的功能,值得注意的是执行定时器后,到指定的时间,记得destroy定时器。
export class mockLocalstorage {
constructor() {
this.store = new Map(); // 记录存储数据
}
getItem(key) {
const stringKey = String(key);
if (this.store.has(stringKey)) {
return String(this.store.get(stringKey));
} else {
return null;
}
}
// time单位是小时
setItem(key, val, time = "undefined") {
let stime = null;
if (typeof time !== "number" && time !== "undefined") {
throw new Error("设置过期时间的基础单位是小时,请不要破坏规则!");
}
if (time !== "undefined") {
time = time * 60 * 60 * 1000; // h ---> ms
try {
let _this = this;
this.store.set(String(key), val);
// 设置定时器 定时清空垃圾数据
stime = setTimeout(() => {
_this.removeItem(key);
stime = null;
}, time);
} catch (e) {
stime = null;
throw new Error(e);
}
}
}
keys() {
return Object.keys(this.store);
}
removeItem(key) {
this.store.delete(String(key));
}
clear() {
this.store.clear();
}
get length() {
return this.store.size;
}
}
测试代码(vue代码)
let _this = this;
let time = null;
this.mockLocalstorage.setItem("name", "duya", 0.01);
console.log(this.mockLocalstorage.getItem("name")); // 测试mockLocalstorage duya
// 检测数据又没有清空😄
time = setTimeout(() => {
time = null;
console.log("name " + _this.mockLocalstorage.getItem("name")); // "name: null"
}, 0.01 * 60 * 60 * 1000);
感觉不能用定时器,因为浏览器会关闭。不知道我感觉的对不对
??存储的时候加个存储时间戳和有效期时长就好了啊。取的时候判断一下不就行了
浏览器切换下一个页签定时器有可能被暂停
使用定时器来做过期时间功能的话,重复设置某个值,如果第二次有效期比第一次长,那么到期之前,第一次已经将它清空了。
加个时间戳和有效期是不是合适一些?基于第 103 题方法的修改
class LocalStorage {
constructor () {
this.store = new Map();
this.expires = new Map();
}
getItem(key) {
const stringKey = String(key);
if (this.store.has(stringKey)) {
let time = this.expires.get(stringKey);
if (!time) {
return String(this.store.get(stringKey));
} else if (Date.now() < (time[0] + time[1])) {
return String(this.store.get(stringKey));
} else {
this.store.delete(stringKey);
this.expires.delete(stringKey);
return null;
}
}
return null;
}
setItem(key, val, time) {
this.store.set(String(key), String(val));
time = Number(time);
if (time && time >= 0) {
this.expires.set(String(key), [Date.now(), time]);
}
}
...
定时器问题,每次set除了要创建新定时器,还要清空已存在的定时器,亦即每次set都会更新定时器,就不必担心旧的定时器清空新set的值了。上面的处理貌似都没在set的时候处理定时器。
更重要的, localStorage要刷新后依然存在。貌似只有cookie模拟的实现了这个特性。
只是可能有以下问题:
localStorage.setItem('myKey', 'value', new Date() + 10000) // 10 秒钟后过期
// 如果再设置以下key,可以让myKey永不过时
localStorage.setItem('myKey_expires', 'abc')
// 或者设置以下key,可以让myKey立即过时
localStorage.setItem('myKey_expires', '')
总之使用关键字的形式,可能会因为特殊key的设置导致其他某些key的过期功能失效或不按预期执行。 所以个人还是更支持用定时器的方式。
可能看起来有点吹毛求疵。 只是实际面试中,面试官看到你的答案后,可能会加问一句,“这个方案可能会有什么潜在问题。 ”
参照楼下的做下总结,实现过期时间 两种常见的方案:
1.时间戳 可能会导致过期功能失效
2.定时器 切换页面时浏览器会自动延迟我们的定时器,以便于节约资源,可能会造成定时器时间不准确
这两种方案,各有优缺点,你会选择哪种
const localStorageMock = (function() { let store = {} return { getItem: function(key) { return store[key] || null }, setItem: function(key, value, time) { // time 是毫秒级别,过时时间必须大于0ms time = Number(time)?time:0; store[key] = value.toString() if(time>0){this.timeOut(key,time)} }, timeOut:function(key,time){ let that = this; let timer = setTimeout(function(){ that.removeItem(key);clearTimeout(timer);},time) }, removeItem: function(key) { delete store[key] }, clear: function() { store = {} }, } })() Object.defineProperty(window, 'localStorage2', { value: localStorageMock }) localStorage2.setItem('test',"test",3000) console.log(localStorage2.getItem("test")) //test setTimeout(function(){ console.log(localStorage2.getItem("test")) //null },4000);
setTimeout不能保证到时间点就执行吧
const localStorage = (function () {
let store = {}
return {
getItem: function (key) {
return store[key] || null
},
setItem: function (key, val, time) {
time = Number(time) || 0;
store[key] = val.toString();
if (time > 0) {
this.timeOut(key, time);
}
},
timeOut: function (key, time) {
var timer = setTimeout(() => {
this.removeItem(key);
clearTimeout(timer)
}, time);
},
removeItem: function (key) {
delete store[key]
},
clear: function () {
store = {}
}
}
})()
Object.defineProperty(window, 'localStorage2', {
value: localStorage
})
class miniLocalStorage {
static unSafeExpires = 2147483647;
static Store = Object.create(null);
static storeExpires = Object.create(null);
constructor() {}
getItem(key) {
return miniLocalStorage.Store[key];
}
setItem(key, val, expires) {
console.log('setItem', Date.now())
try {
if (typeof expires === 'number' && !Number.isNaN(expires)) {
if (expires > miniLocalStorage.unSafeExpires) {
throw Error('expires overflow');
}
this._clearTimeout(key)
miniLocalStorage.storeExpires[key + '_timer'] = setTimeout(()=>{
delete miniLocalStorage.Store[key]
}
, expires);
}
miniLocalStorage.Store[key] = val
} catch (e) {
throw e;
}
}
removeItem(key) {
this._clearTimeout(key)
delete miniLocalStorage.Store[key];
}
clear() {
for (const key in miniLocalStorage.Store) {
this.removeItem(key)
}
}
_clearTimeout(key) {
const expiresId = key + '_timer'
if (expiresId in miniLocalStorage.storeExpires) {
clearTimeout(miniLocalStorage.storeExpires[expiresId])
delete miniLocalStorage.storeExpires[expiresId];
}
}
}
var miniLocalStorage2 = (function() {
const Store = Object.create(null);
function miniLocalStorage2() {}
miniLocalStorage2.prototype.getItem = function getItem(key) {
if (Store[key]) {
if ('expires' in Store[key] && Date.now() > Store[key].expires) {
delete Store[key]
} else {
return Store[key].value;
}
}
}
miniLocalStorage2.prototype.setItem = function setItem(key, value, expires) {
Store[key] = {
value
};
console.log('setItem', Date.now())
if (typeof expires === 'number' && !Number.isNaN(expires)) {
Store[key].expires = (Date.now() + expires);
}
}
miniLocalStorage2.prototype.removeItem = function removeItem(key) {
delete Store[key];
}
miniLocalStorage2.prototype.clear = function clear() {
for (const key in Store) {
this.removeItem(key)
}
}
return miniLocalStorage2;
}
)()
localStorage最主要的能力是本地存储,即使关闭浏览器还是存在的,因此基于cookie做个模拟
function setCookie(key, value = '', days) {
if (!key) return
let date = new Date()
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); // 设置过期时间为 days 天之后
document.cookie = key + "=" + decodeURIComponent(value) + ";expires=" + date.toGMTString();
}
function getCookie(key) {
let result = ''
let cookie = document.cookie + ";"
let queryString = key + "="
let start = cookie.indexOf(queryString)
if (start > -1) {
start += queryString.length
let end = cookie.indexOf(";", start)
result = (cookie.substring(start, end))
}
// return getCookies()[key]
return result
}
function getCookies() {
let result = {}
if (!document.cookie) return result
document.cookie.split(';').forEach((item) => {
const [key, value] = item.split('=')
result[key.trim()] = value.trim()
})
return result
}
window.localStorage = window.localStorage || new LocalStorage()
function LocalStorage() {
const cookies = getCookies()
this.length = Object.keys(cookies).length
for (let [key, value] of Object.entries(cookies)) {
this[key] = value
}
}
LocalStorage.prototype = function () {
function getItem(key) {
return getCookie(key);
}
function setItem(key, value, days) {
this[key] = value
this.length++
setCookie(key, value, days)
}
function removeItem(key) {
this.length--
delete this[key]
setItem(key, '', -1);
}
function clear() {
const cookies = getCookies();
for (let key of Object.keys(cookies)) {
delete this[key]
removeItem(key);
}
this.length = 0
}
return {
constructor: LocalStorage,
getItem,
setItem,
removeItem,
clear,
}
}()
其实只需要在获取数据的时候判断数据的时间是否已经过期就好了,最多再加一个time interval 去定时遍历数据的过期时间。
在set key的时候 加上一个timestamp
获取的时候拿这个timestampe进行时间判断 看是否过期
随便写了一个,麻烦大家看一下有没有不对。setItem的时候同时保存过期时间戳,同时可以根据需要是否自行定时检查。主要写一下思路,没有做过多的值的合法检查,关于值的合法检查的问题就不要批评我了。
class ExpirableLocalStorage {
/**
* 设置localStorage键值对
* @param {Integer} expires 过期时间戳
*/
static setItem(key, value, expires) {
if (expires < Date.now()) {
this.removeItem(key);
return;
}
window.localStorage.setItem(key, value);
window.localStorage.setItem(`expires_${key}`, expires);
}
/**
* 获取localStorage值
*/
static getItem(key) {
if (parseInt(window.localStorage.getItem(`expires_${key}`)) < Date.now()) {
this.removeItem(key);
return null;
}
return window.localStorage.getItem(key);
}
/**
* 移除localStorage键值对
*/
static removeItem(key) {
window.localStorage.removeItem(key);
window.localStorage.removeItem(`expires_${key}`);
}
/**
* 检查过期的键值对并移除
*/
static checkExpired() {
for (let i = 0, len = window.localStorage.length; i < len; i++) {
const key = window.localStorage.key(i);
const value = window.localStorage.getItem(key);
const expires = window.localStorage.getItem(`expires_${key}`);
if (value && expires && expires < Date.now()) {
this.removeItem(key);
}
}
}
/**
* 定时检查
*/
static timingCheck(interval = 1000) {
if (ExpirableLocalStorage.timer) {
clearInterval(ExpirableLocalStorage.timer);
}
ExpirableLocalStorage.timer = setInter(() => {
this.checkExpired();
}, interval);
}
}
用 cookie 模拟 localStorage
参考 https://developer.mozilla.org/zh-CN/docs/Web/API/Storage/LocalStorage
if (!window.localStorage) { window.localStorage = { getItem: function (sKey) { if (!sKey || !this.hasOwnProperty(sKey)) { return null; } return unescape(document.cookie.replace(new RegExp("(?:^|.*;\\s*)" + escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*((?:[^;](?!;))*[^;]?).*"), "$1")); }, key: function (nKeyId) { return unescape(document.cookie.replace(/\s*\=(?:.(?!;))*$/, "").split(/\s*\=(?:[^;](?!;))*[^;]?;\s*/)[nKeyId]); }, setItem: function (sKey, sValue) { if(!sKey) { return; } document.cookie = escape(sKey) + "=" + escape(sValue) + "; expires=Tue, 19 Jan 2038 03:14:07 GMT; path=/"; this.length = document.cookie.match(/\=/g).length; }, length: 0, removeItem: function (sKey) { if (!sKey || !this.hasOwnProperty(sKey)) { return; } document.cookie = escape(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/"; this.length--; }, hasOwnProperty: function (sKey) { return (new RegExp("(?:^|;\\s*)" + escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie); } }; window.localStorage.length = (document.cookie.match(/\=/g) || window.localStorage).length; }扩展 localStorage 支持 expires
(function () { var getItem = localStorage.getItem.bind(localStorage) var setItem = localStorage.setItem.bind(localStorage) var removeItem = localStorage.removeItem.bind(localStorage) localStorage.getItem = function (keyName) { var expires = getItem(keyName + '_expires') if (expires && new Date() > new Date(Number(expires))) { removeItem(keyName) removeItem(keyName + '_expires') } return getItem(keyName) } localStorage.setItem = function (keyName, keyValue, expires) { if (typeof expires !== 'undefined') { var expiresDate = new Date(expires).valueOf() setItem(keyName + '_expires', expiresDate) } return setItem(keyName, keyValue) } })()使用
localStorage.setItem('key', 'value', new Date() + 10000) // 10 秒钟后过期 localStorage.getItem('key')
使用方法好像expire参数需要修改一下:
localStorage.setItem('key', 'value', new Date(Date.now() + 10000)) // 10 秒钟后过期
模拟cookie功能,之前写了个lib: https://github.com/pagemarks/codes/blob/master/js/cache.js
function getStorage(isSession) {
return isSession ? sessionStorage : localStorage;
}
const cache = {
get(key, isSession = true, val = null) {
const storage = getStorage(isSession);
let ret = storage.getItem(key);
if (!ret && val !== null) return val; //default val
const char = ret && ret.slice(0, 1);
if (char && (char === '{' || char === '[')) {
ret = JSON.parse(ret);
if (ret.expires) {
if (ret.expires >= Date.now()) {
if ('value' in ret && Object.keys(ret).length === 2) {
ret = ret.value;
} else {
delete ret.expires;
}
} else {
ret = val !== null ? val : null;
this.del(key, isSession)
}
}
} else if (ret === 'true' || ret=== 'false') {
ret = ret === 'true';
}
return ret;
},
hget(key, hash, isSession = true) {
return this.get(key, isSession, {})[hash];
},
set(key, value, isSession = true, seconds = 0) {
const storage = getStorage(isSession);
let val = value;
if (seconds) {
const expires = Date.now() + seconds;
val = Object.assign({}, typeof value === 'object' ? value : { value }, { expires });
}
if (typeof value === 'object') val = JSON.stringify(val);
storage.setItem(key, val);
},
hset(key, hash, val, isSession = true) {
const ob = this.get(key, isSession, {});
ob[hash] = val;
this.set(key, ob, isSession);
},
del(key, isSession = true) {
const storage = getStorage(isSession);
storage.removeItem(key);
}
};
export default cache;
cookie 只能存那么一点东西, 你有cookie模拟意义在哪里
// Custom storage object
const storage: StateStorage = {
getItem(key) {
const itemStr = localStorage.getItem(key)
// if the item doesn't exist, return null
if (!itemStr) {
return null
}
const item = JSON.parse(itemStr)
const now = new Date()
// compare the expiry time of the item with the current time
if (now.getTime() > item.expiry) {
// If the item is expired, delete the item from storage
// and return null
localStorage.removeItem(key)
return null
}
return item.value
},
setItem(key, value) {
const now = new Date()
// `item` is an object which contains the original value
// as well as the time when it's supposed to expire
const item = {
value: value,
expiry: now.getTime() + 7 * 24 * 60 * 60 * 1000, // 7 days
}
localStorage.setItem(key, JSON.stringify(item))
},
// remove the entry with the given key
removeItem(key) {
window.localStorage.removeItem(key)
},
}