第 103 题:模拟实现一个 localStorage
yygmind opened this issue · 36 comments
const localStorageMock = (function() {
let store = {}
return {
getItem: function(key) { return store[key] || null },
setItem: function(key, value) { store[key] = value.toString() },
removeItem: function(key) { delete store[key] },
clear: function() { store = {} },
}
})()
Object.defineProperty(window, 'localStorage2', {
value: localStorageMock
})
localStorage2.setItem('test', 'test')
console.log(localStorage2.getItem("test")) //test
localStorage2.removeItem('test')
console.log(localStorage2.getItem("test")) //null
localStorage2.setItem('test', 'test')
localStorage2.clear()
console.log(localStorage2.getItem("test")) //null
` const localStorageMock = (function() {
let store = {}
return {
getItem: function(key) { return store[key] || null },
setItem: function(key, value) { store[key] = value.toString() },
removeItem: function(key) { delete store[key] },
clear: function() { store = {} },
}
})()Object.defineProperty(window, 'localStorage2', { value: localStorageMock }) localStorage2.setItem('test', 'test') console.log(localStorage2.getItem("test")) //test localStorage2.removeItem('test') console.log(localStorage2.getItem("test")) //null localStorage2.setItem('test', 'test') localStorage2.clear() console.log(localStorage2.getItem("test")) //null`
没有校验 value 是否为字符串
` const localStorageMock = (function() {
let store = {}
return {
getItem: function(key) { return store[key] || null },
setItem: function(key, value) { store[key] = value.toString() },
removeItem: function(key) { delete store[key] },
clear: function() { store = {} },
}
})()Object.defineProperty(window, 'localStorage2', { value: localStorageMock }) localStorage2.setItem('test', 'test') console.log(localStorage2.getItem("test")) //test localStorage2.removeItem('test') console.log(localStorage2.getItem("test")) //null localStorage2.setItem('test', 'test') localStorage2.clear() console.log(localStorage2.getItem("test")) //null`
没有校验 value 是否为字符串
因为自带的localStorage也是把对象自动转成字符串的。我测试过自带的localStorage也是这样子。
'use strict'
const valuesMap = new Map()
class LocalStorage {
getItem (key) {
const stringKey = String(key)
if (valuesMap.has(key)) {
return String(valuesMap.get(stringKey))
}
return null
}
setItem (key, val) {
valuesMap.set(String(key), String(val))
}
removeItem (key) {
valuesMap.delete(key)
}
clear () {
valuesMap.clear()
}
key (i) {
if (arguments.length === 0) {
throw new TypeError("Failed to execute 'key' on 'Storage': 1 argument required, but only 0 present.") // this is a TypeError implemented on Chrome, Firefox throws Not enough arguments to Storage.key.
}
var arr = Array.from(valuesMap.keys())
return arr[i]
}
get length () {
return valuesMap.size
}
}
const instance = new LocalStorage()
global.localStorage = new Proxy(instance, {
set: function (obj, prop, value) {
if (LocalStorage.prototype.hasOwnProperty(prop)) {
instance[prop] = value
} else {
instance.setItem(prop, value)
}
return true
},
get: function (target, name) {
if (LocalStorage.prototype.hasOwnProperty(name)) {
return instance[name]
}
if (valuesMap.has(name)) {
return instance.getItem(name)
}
}
})
class mockLocalStorage {
constructor() {
this.store = {}
}
getItem(key) {
return this.store[key] || null
}
setItem(key,value) {
this.store[key] = value
}
removeItem(key) {
delete this.store[key]
}
clear() {
this.store = {}
}
}
window.localStorage2 = new mockLocalStorage()
这答案吓到我了。存储过之后,刷新页面,还能取出来吗???
'use strict' const valuesMap = new Map() class LocalStorage { getItem (key) { const stringKey = String(key) if (valuesMap.has(key)) { return String(valuesMap.get(stringKey)) } return null } setItem (key, val) { valuesMap.set(String(key), String(val)) } removeItem (key) { valuesMap.delete(key) } clear () { valuesMap.clear() } key (i) { if (arguments.length === 0) { throw new TypeError("Failed to execute 'key' on 'Storage': 1 argument required, but only 0 present.") // this is a TypeError implemented on Chrome, Firefox throws Not enough arguments to Storage.key. } var arr = Array.from(valuesMap.keys()) return arr[i] } get length () { return valuesMap.size } } const instance = new LocalStorage() global.localStorage = new Proxy(instance, { set: function (obj, prop, value) { if (LocalStorage.prototype.hasOwnProperty(prop)) { instance[prop] = value } else { instance.setItem(prop, value) } return true }, get: function (target, name) { if (LocalStorage.prototype.hasOwnProperty(name)) { return instance[name] } if (valuesMap.has(name)) { return instance.getItem(name) } } })
代理中这个判断又什么作用呀
'use strict' const valuesMap = new Map() class LocalStorage { getItem (key) { const stringKey = String(key) if (valuesMap.has(key)) { return String(valuesMap.get(stringKey)) } return null } setItem (key, val) { valuesMap.set(String(key), String(val)) } removeItem (key) { valuesMap.delete(key) } clear () { valuesMap.clear() } key (i) { if (arguments.length === 0) { throw new TypeError("Failed to execute 'key' on 'Storage': 1 argument required, but only 0 present.") // this is a TypeError implemented on Chrome, Firefox throws Not enough arguments to Storage.key. } var arr = Array.from(valuesMap.keys()) return arr[i] } get length () { return valuesMap.size } } const instance = new LocalStorage() global.localStorage = new Proxy(instance, { set: function (obj, prop, value) { if (LocalStorage.prototype.hasOwnProperty(prop)) { instance[prop] = value } else { instance.setItem(prop, value) } return true }, get: function (target, name) { if (LocalStorage.prototype.hasOwnProperty(name)) { return instance[name] } if (valuesMap.has(name)) { return instance.getItem(name) } } })代理中这个判断又什么作用呀
localStorage不就这样么,你可以拿个浏览器看一下,这个比较细节了
` const localStorageMock = (function() {
let store = {}
return {
getItem: function(key) { return store[key] || null },
setItem: function(key, value) { store[key] = value.toString() },
removeItem: function(key) { delete store[key] },
clear: function() { store = {} },
}
})()Object.defineProperty(window, 'localStorage2', { value: localStorageMock }) localStorage2.setItem('test', 'test') console.log(localStorage2.getItem("test")) //test localStorage2.removeItem('test') console.log(localStorage2.getItem("test")) //null localStorage2.setItem('test', 'test') localStorage2.clear() console.log(localStorage2.getItem("test")) //null`
没有校验 value 是否为字符串
因为自带的localStorage也是把对象自动转成字符串的。我测试过自带的localStorage也是这样子。
localStorage.setItem('test',{a:'a'})
` const localStorageMock = (function() {
let store = {}
return {
getItem: function(key) { return store[key] || null },
setItem: function(key, value) { store[key] = value.toString() },
removeItem: function(key) { delete store[key] },
clear: function() { store = {} },
}
})()Object.defineProperty(window, 'localStorage2', { value: localStorageMock }) localStorage2.setItem('test', 'test') console.log(localStorage2.getItem("test")) //test localStorage2.removeItem('test') console.log(localStorage2.getItem("test")) //null localStorage2.setItem('test', 'test') localStorage2.clear() console.log(localStorage2.getItem("test")) //null`
没有校验 value 是否为字符串
因为自带的localStorage也是把对象自动转成字符串的。我测试过自带的localStorage也是这样子。
localStorage.setItem('test',{a:'a'})
我的也是这样的,你可以试一下啊。
'use strict' const valuesMap = new Map() class LocalStorage { getItem (key) { const stringKey = String(key) if (valuesMap.has(key)) { return String(valuesMap.get(stringKey)) } return null } setItem (key, val) { valuesMap.set(String(key), String(val)) } removeItem (key) { valuesMap.delete(key) } clear () { valuesMap.clear() } key (i) { if (arguments.length === 0) { throw new TypeError("Failed to execute 'key' on 'Storage': 1 argument required, but only 0 present.") // this is a TypeError implemented on Chrome, Firefox throws Not enough arguments to Storage.key. } var arr = Array.from(valuesMap.keys()) return arr[i] } get length () { return valuesMap.size } } const instance = new LocalStorage() global.localStorage = new Proxy(instance, { set: function (obj, prop, value) { if (LocalStorage.prototype.hasOwnProperty(prop)) { instance[prop] = value } else { instance.setItem(prop, value) } return true }, get: function (target, name) { if (LocalStorage.prototype.hasOwnProperty(name)) { return instance[name] } if (valuesMap.has(name)) { return instance.getItem(name) } } })代理中这个判断又什么作用呀
localStorage 可以通过 localStorage.xxx 方式取值。
https://html.spec.whatwg.org/multipage/webstorage.html#dom-localstorage
这个是localStorage的定义似乎还挺复杂的
难道不是应该用 cookie 模拟 localStorage 吗(兼容 IE8 及以下的 polyfill)?
!window.localStorage && !function(win) {
var thousandYears = 1e3 * 365 * 24 * 36e5;
function getCookies() {
return document.cookie.match(/([^;=]+)=([^;]+)/g) || [];
}
function getExpires(flag) {
flag = flag || 1;
return 'expires=' +
(new Date((+new Date()) + thousandYears * flag)).toUTCString();
}
function get(key) {
var cookies = getCookies();
for (var i = 0; i < cookies.length; i++) {
var param = cookies[i].match(/^\s*([^=]+)=(.+)/);
if (param[1] === String(key)) {
return decodeURIComponent(param[2]);
}
}
return null;
}
function set(key, value, isExpired) {
document.cookie = [
key + '=' + encodeURIComponent(value),
getExpires(isExpired ? -1 : 1),
'path=/'
].join('; ');
}
function remove(key) {
set(key, '', true);
}
function clear() {
var cookies = getCookies();
for (var i = 0; i < cookies.length; i++) {
var key = cookies[i].match(/^\s*([^=]+)/)[1];
remove(key);
}
}
// 注册到 window 对象上
win.localStorage = {
getItem: get,
setItem: set,
removeItem: remove,
clear: clear
};
}(window);
难道不是应该用 cookie 模拟 localStorage 吗(兼容 IE8 及以下的 polyfill)?
!window.localStorage && !function(win) { var thousandYears = 1e3 * 365 * 24 * 36e5; function getCookies() { return document.cookie.match(/([^;=]+)=([^;]+)/g) || []; } function getExpires(flag) { flag = flag || 1; return 'expires=' + (new Date((+new Date()) + thousandYears * flag)).toUTCString(); } function get(key) { var cookies = getCookies(); for (var i = 0; i < cookies.length; i++) { var param = cookies[i].match(/^\s*([^=]+)=(.+)/); if (param[1] === String(key)) { return decodeURIComponent(param[2]); } } return null; } function set(key, value, isExpired) { document.cookie = [ key + '=' + encodeURIComponent(value), getExpires(isExpired ? -1 : 1), 'path=/' ].join('; '); } function remove(key) { set(key, '', true); } function clear() { var cookies = getCookies(); for (var i = 0; i < cookies.length; i++) { var key = cookies[i].match(/^\s*([^=]+)/)[1]; remove(key); } } // 注册到 window 对象上 win.localStorage = { getItem: get, setItem: set, removeItem: remove, clear: clear }; }(window);
@wingmeng 使用cookie解决了刷新浏览器存储信息不被清除的问题,但是这样存储的信息就参与了服务器的通信,增加了请求负担,这个问题需要考虑吗
-
same-origin rules 特定于页面的协议,还有隐身模式的区别
当浏览器进入隐身模式(private browsing mode)的时候,会创建一个新的、临时的、空的数据库,用以存储本地数据(local storage data)。当浏览器关闭时,里面的所有数据都将被丢弃。 -
模拟持久存储
a. like Internet Explorer < 8. It also makes use of cookies.
b. IndexedDB
c. WebSQL -
Storage Interface
interface Storage {
readonly attribute unsigned long length;
[IndexGetter] DOMString key(in unsigned long index);
[NameGetter] DOMString getItem(in DOMString key);
[NameSetter] void setItem(in DOMString key, in DOMString data);
[NameDeleter] void removeItem(in DOMString key);
void clear();
};
- localStorage 中的键值对总是以字符串的形式存储 key.toString() value.toString()
localStorage.setItem('a', {a:1})
undefined
localStorage.getItem('a')
"[object Object]"
a={a:1}
localStorage.setItem(a, {a:1})
{[object Object]: "[object Object]"}
localStorage.setItem('a', document.body)
localStorage.getItem('a')
"[object HTMLBodyElement]"
// 模拟实现一个 localStorage
const localStorage = (function(){
let store = {};
return {
getItem(key){
return store[key] || null;
},
setItem(key,value){
store[key] = value.toString();
},
removeItem(key){
delete store[key]
},
clear(){
store = {};
}
}
})()
` const localStorageMock = (function() {
let store = {}
return {
getItem: function(key) { return store[key] || null },
setItem: function(key, value) { store[key] = value.toString() },
removeItem: function(key) { delete store[key] },
clear: function() { store = {} },
}
})()Object.defineProperty(window, 'localStorage2', { value: localStorageMock }) localStorage2.setItem('test', 'test') console.log(localStorage2.getItem("test")) //test localStorage2.removeItem('test') console.log(localStorage2.getItem("test")) //null localStorage2.setItem('test', 'test') localStorage2.clear() console.log(localStorage2.getItem("test")) //null`
没有校验 value 是否为字符串
因为自带的localStorage也是把对象自动转成字符串的。我测试过自带的localStorage也是这样子。
楼主写的基本上跟浏览器自带的功能一样了,localStorage.setItem存一个对象时一般要加上JSON.stringify序列化下,localStorage.getItem取一个对象时再用JSON.parse反序列化下就可以了。
localStorage 难道最主要的功能不是持久化存储么?还可以在控制台手动删除。上面的答案没有一个能打的啊
localStorage 难道最主要的功能不是持久化存储么?还可以在控制台手动删除。上面的答案没有一个能打的啊
你要知道,js的权限是被限制的,如果不用浏览器自带的,是不可能持久化的。
localStorage 难道最主要的功能不是持久化存储么?还可以在控制台手动删除。上面的答案没有一个能打的啊
你要知道,js的权限是被限制的,如果不用浏览器自带的,是不可能持久化的。
所以上面模拟的都是啥,就实现一个api也叫模拟么?就不说持久化存储好了,最基本的容量限制也没处理吧?
localStorage 难道最主要的功能不是持久化存储么?还可以在控制台手动删除。上面的答案没有一个能打的啊
你要知道,js的权限是被限制的,如果不用浏览器自带的,是不可能持久化的。
所以上面模拟的都是啥,就实现一个api也叫模拟么?就不说持久化存储好了,最基本的容量限制也没处理吧?
这,要不你设计一个,我们只是给自己的想法而已,有缺陷是很正常的啊。
使用Map数据结构构造localstorage
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;
}
}
setItem(key, val) {
try {
this.store.set(String(key), val);
} catch (e) {
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;
}
}
测试
this.mockLocalstorage = new mockLocalstorage();
this.mockLocalstorage.setItem("name", "duya");
console.log(this.mockLocalstorage.getItem("name")); // 测试mockLocalstorage duya
global.localStorage = new Proxy(instance, { set: function (obj, prop, value) { if (LocalStorage.prototype.hasOwnProperty(prop)) { instance[prop] = value } else { instance.setItem(prop, value) } return true }, get: function (target, name) { if (LocalStorage.prototype.hasOwnProperty(name)) { return instance[name] } if (valuesMap.has(name)) { return instance.getItem(name) } } })
为什么要用proxy啊,它的作用是什么呢?求解
'use strict' const valuesMap = new Map() class LocalStorage { getItem (key) { const stringKey = String(key) if (valuesMap.has(key)) { return String(valuesMap.get(stringKey)) } return null } setItem (key, val) { valuesMap.set(String(key), String(val)) } removeItem (key) { valuesMap.delete(key) } clear () { valuesMap.clear() } key (i) { if (arguments.length === 0) { throw new TypeError("Failed to execute 'key' on 'Storage': 1 argument required, but only 0 present.") // this is a TypeError implemented on Chrome, Firefox throws Not enough arguments to Storage.key. } var arr = Array.from(valuesMap.keys()) return arr[i] } get length () { return valuesMap.size } } const instance = new LocalStorage() global.localStorage = new Proxy(instance, { set: function (obj, prop, value) { if (LocalStorage.prototype.hasOwnProperty(prop)) { instance[prop] = value } else { instance.setItem(prop, value) } return true }, get: function (target, name) { if (LocalStorage.prototype.hasOwnProperty(name)) { return instance[name] } if (valuesMap.has(name)) { return instance.getItem(name) } } })代理中这个判断又什么作用呀
localStorage 可以通过 localStorage.xxx 方式取值。
不用proxy,也可以localStorage.xxx方式取值啊~~~
localStorage 难道最主要的功能不是持久化存储么?还可以在控制台手动删除。上面的答案没有一个能打的啊
你要知道,js的权限是被限制的,如果不用浏览器自带的,是不可能持久化的。
所以上面模拟的都是啥,就实现一个api也叫模拟么?就不说持久化存储好了,最基本的容量限制也没处理吧?
这,要不你设计一个,我们只是给自己的想法而已,有缺陷是很正常的啊。
这个题目设计的没有意义。 我一看题目就觉得实现的是持久化存储
如题(localStorage)的核心功能没有体现,持久化存储不是js模拟能实现的,浏览器自带功能还是借助浏览器吧
'use strict' const valuesMap = new Map() class LocalStorage { getItem (key) { const stringKey = String(key) if (valuesMap.has(key)) { return String(valuesMap.get(stringKey)) } return null } setItem (key, val) { valuesMap.set(String(key), String(val)) } removeItem (key) { valuesMap.delete(key) } clear () { valuesMap.clear() } key (i) { if (arguments.length === 0) { throw new TypeError("Failed to execute 'key' on 'Storage': 1 argument required, but only 0 present.") // this is a TypeError implemented on Chrome, Firefox throws Not enough arguments to Storage.key. } var arr = Array.from(valuesMap.keys()) return arr[i] } get length () { return valuesMap.size } } const instance = new LocalStorage() global.localStorage = new Proxy(instance, { set: function (obj, prop, value) { if (LocalStorage.prototype.hasOwnProperty(prop)) { instance[prop] = value } else { instance.setItem(prop, value) } return true }, get: function (target, name) { if (LocalStorage.prototype.hasOwnProperty(name)) { return instance[name] } if (valuesMap.has(name)) { return instance.getItem(name) } } })
localStorage.key(i)访问key的时候,越界返回null,arr[i]越界返回undefined,需要修正这一点。
如果是要求实现localStorage的持久化存储功能....这是不可能的; 如果不是,那么这个题....好像没有什么意义啊,最多考察一下语法
标注是阿里的题,明显考察的是用cookie实现polyfill,为啥一堆人认为在考api语法...
const localStorageMock = (function() { let store = {} return { getItem: function(key) { return store[key] || null }, setItem: function(key, value) { store[key] = value.toString() }, removeItem: function(key) { delete store[key] }, clear: function() { store = {} }, } })() Object.defineProperty(window, 'localStorage2', { value: localStorageMock }) localStorage2.setItem('test', 'test') console.log(localStorage2.getItem("test")) //test localStorage2.removeItem('test') console.log(localStorage2.getItem("test")) //null localStorage2.setItem('test', 'test') localStorage2.clear() console.log(localStorage2.getItem("test")) //null
可是这样无法实现持久存储啊
全都没有考虑持久化存储呀, 我的想法是通过cookie或者indexedDB
使用indexedDB 模拟实现一个基础的 localStorage
index.html,添加一个触发按钮
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<button>set</button>
</body>
</html>
<script src="./localStorageMock.js"></script>
localStorageMock.js
const name = Symbol('localStorageMock');
const localStorageMock = new (class {
constructor() {
Object.assign(this, { [name]: {}, db: {}, objectStore: {}, result: '' });
Object.defineProperties(this, {
DBOpenRequest: {
value: window.indexedDB.open('localStorageMock', 1),
writable: false,
configurable: false,
},
});
this.DBOpenRequest.onupgradeneeded = event => {
this.db = event.target.result;
if (!this.db.objectStoreNames.contains('store')) {
this.objectStore = this.db.createObjectStore('store', { keyPath: 'name' });
this.objectStore.createIndex('name', 'name', { unique: false });
}
};
this.DBOpenRequest.onerror = event => console.error(event);
this.DBOpenRequest.onsuccess = event => {
this.db = event.target.result;
};
}
/** 获取所有Storage数据列表 */
readAll() {
return new Promise(resolve => {
let objectStore = this.db.transaction('store').objectStore('store');
let arr = [];
objectStore.openCursor().onsuccess = event => {
let cursor = event.target.result;
if (cursor) {
arr.push(cursor.value);
cursor.continue();
return resolve(arr);
} else {
console.log('没有更多数据了!');
}
};
});
}
getItem(key) {
return new Promise((resolve, reject) => {
let request = this.db
.transaction(['store'], 'readonly')
.objectStore('store')
.index('name')
.get(key);
request.onerror = () => reject('事务失败');
request.onsuccess = function(event) {
if (request.result) {
return resolve(request.result);
} else {
return reject('未获得数据记录');
}
};
});
}
/** 注意隐患,刷新会情况this[name]数据,建议直接setItem */
add(key, value) {
console.log(this[name].hasOwnProperty(key));
if (this[name].hasOwnProperty(key)) {
return this.setItem(key, value);
}
Object.assign(this[name], { [key]: value });
let request = this.db
.transaction(['store'], 'readwrite')
.objectStore('store')
.add({ name: key, value: value });
request.onsuccess = event => console.log('数据写入成功', event.target.result);
request.onerror = err => console.error('数据写入失败', err.target.error);
}
removeItem(key) {
let request = this.db
.transaction(['store'], 'readwrite')
.objectStore('store')
.delete(key);
request.onsuccess = () => console.log('数据删除成功');
request.onerror = err => console.error('数据删除失败', err);
}
clear() {
let request = this.db
.transaction(['store'], 'readwrite')
.objectStore('store')
.clear();
request.onsuccess = () => console.log('数据库清除成功');
request.onerror = err => console.error('数据库清除失败', err);
}
setItem(key, value) {
let request = this.db
.transaction(['store'], 'readwrite')
.objectStore('store')
.put({ name: key, value: value });
request.onsuccess = () => console.log('数据写入成功');
request.onerror = err => console.error('数据写入失败', err);
}
})();
document.getElementsByTagName('button')[0].addEventListener('click', async function() {
console.log(localStorageMock);
await localStorageMock.setItem('小明', '22岁');
console.log(await localStorageMock.getItem('小明'));
console.log(await localStorageMock.readAll());
await localStorageMock.removeItem('小明');
await localStorageMock.clear();
});