浏览器数据存储方式
常用的前端数据存储方法笼统来说有 3 种:
- local/session storage
- cookies
- indexeddb
3 种方法各有各的优点和使用范围。
local/session storage
local/session storage 保存的格式都为键值对,并且用法都是差不多,如下:
const userid = 'u12345';
const user = {
name: 'name',
age: 18,
status: 'active',
};
document.querySelector('#store').addEventListener('click', () => {
sessionStorage.setItem('userid', userid);
sessionStorage.setItem('user', user);
});
document.querySelector('#extract').addEventListener('click', () => {
console.log(sessionStorage);
});
当 store
被点击后,就会将对应的数据存储到对应的 storage 中:
需要注意的是,这里的问题就在于 user
没有存进去,这是因为 local/session storage 没有办法存储复杂的对象,而是只能保存字符串,所以正确的保存方式为:
document.querySelector('#store').addEventListener('click', () => {
sessionStorage.setItem('userid', userid);
sessionStorage.setItem('user', JSON.stringify(user));
});
document.querySelector('#extract').addEventListener('click', () => {
console.log(sessionStorage.getItem('userid'));
console.log(JSON.parse(sessionStorage.getItem('user')));
});
local storage 和 session storage 的区别就在于,local storage 相当于做了一个本地的持久化,其中的数据如果不是通过 js 代码、用户删除,浏览器不会主动删除。而 session storage 则是在 session 结束之后(用户关闭当前 tab)后自动删除,如:
这种存储方法优点是:
-
可以存储相对比较大的数据(通常来说 5-10mb)
没记错的话,leetcode 的代码就是存在 local storage 里的
-
可以根据需求选择使用 session 或是 local
local storage 存储的数据可以在不同的 tabs 之间被共享
-
使用方式简单, API 支持更好
这种存储方法缺点是:
- tabs 之间数据无法共享(针对 session storage)
- 服务端无法获取数据(除非添加到 header/body 中传输到后端)
- CORS,不同 domain 之间数据无法共享
cookies
cookies 是另一种存储方式,它存储的也是一个键值对,使用方式如下:
const userid = 'u12345';
const user = {
name: 'name',
age: 18,
status: 'active',
};
document.querySelector('#store').addEventListener('click', () => {
document.cookie = `uid=${userid}`;
document.cookie = `user=${JSON.stringify(user)}`;
});
document.querySelector('#extract').addEventListener('click', () => {
console.log(document.cookie.split(';').map((i) => i.trim()));
});
cookies 同样可以设置生存周期,如:
document.cookie = `uid=${userid}; max-age=50`;
这段就会让 cookie 在 50s 后过期:
另外还有一种 cookie 叫做 httpOnly
,这种 cookie 多数用于后端与浏览器之间的交流,无法直接通过 JS 获取。
这种存储方法优点是:
-
服务端可以获取 cookies
-
可以用于验证和认证
虽然这么说,不过因为发起 request 就会携带 cookies,所以大多数情况下 cookies 不会保存敏感信息(病毒代码可以 request 到一些第三方站点,这样会导致数据泄露),只会保留过期时间之类的不是非常敏感的数据
目前主流代替使用 cookie 的验证方法有 JWT
-
跨域交流
这点上面提到了,病毒代码其实是可以携带 cookie 去访问有问题的网页的,不过相对于 local/session 来说,这个的确是优点
-
可以存活多个 sessions
-
可用于追踪用户数据
访问一些网站的话,它们也会“请求”cookies 的数据去“提供更好的服务”
这种存储方法缺点是:
-
数据量小
一个 cookie 只有 4kb
-
多个 cookie 可以导致 HTTP 请求过大
因为 cookie 会存在于每个请求中
-
安全、隐私考虑
包括追踪用户信息和跨域交流,前者是个人信息,后者容易导致 XSS
-
灵活性低
不同于 local/session storage,有提供良好的 API 去获取数据,cookies 必须要手动进行 split,在不使用第三方库的情况下获取 cookie 较为麻烦
indexedDB
indexedDB 是这里会提到的最后一个比较主流的数据存储解决方案,但是大多数情况下并不会用到,在我个人的实际项目经验里用到的只有一次:为了提供 offline features。当然,感兴趣的也可以看看 vscode 的源码,我之前有瞄到 vscode 也用 indexedDB。
indexedDB 是一个 NoSQL DB,不过这块这里不会细究。
它的使用方式如下:
const userid = 'u12345';
const user = {
name: 'name',
age: 18,
status: 'active',
};
// non promise based
// will create db if the db doesn't exist, otherwise just open it
const dbRequest = indexedDB.open('StorageDemo', 1);
let db = null;
dbRequest.addEventListener('success', (e) => {
db = e.target.result;
});
// calls each time when db version changes or initialized
dbRequest.addEventListener('upgradeneeded', (e) => {
db = e.target.result;
const objStore = db.createObjectStore('products', {
keyPath: 'id',
});
objStore.transaction.addEventListener('complete', (e) => {
const productStore = db
.transaction('products', 'readwrite')
.objectStore('products');
productStore.add({ id: 'p1', title: 'First Product', price: 99.99 });
});
});
dbRequest.addEventListener('error', (e) => {
console.log(e);
});
document.querySelector('#store').addEventListener('click', () => {
if (!db) return;
const productStore = db
.transaction('products', 'readwrite')
.objectStore('products');
productStore.add({ id: 'p2', title: 'Second Product', price: 9.99 });
});
document.querySelector('#extract').addEventListener('click', () => {
const productStore = db
.transaction('products', 'readonly')
.objectStore('products');
const request = productStore.get('p2');
request.addEventListener('success', () => {
console.log(request.result);
});
});
语法就是这么的麻烦,并且所有的执行都是在 callback 中,同样因为使用太复杂了,所以基本上都会用 wrapper 进行操作,读写的结果如下:
indexedDB 的优点:
-
数据存储量大
据说每个 domain 可以存到 1GB,浏览器可以使用 60% 左右的硬盘内存
-
存储数据格式灵活
不需要 stringfy,可以直接以对象、数组的方式存储,同时因为是 NoSQL,所以存储的对象格式也非常灵活
-
异步操作
非阻塞式运行(这也是为什么这么多 callbacks)
-
数据库优势
包括 ACID、indexed search 和 query
indexedDB 的缺点:
-
特别麻烦……
语法麻烦,query 麻烦,异步也麻烦……
所以也就导致开发麻烦,维护麻烦,而且因为 callbacks 太多了,如果使用 JS 提示不太好,我这里代码就写错了,但是 JS 完全没办法提示:
正确的语法应该使用
objectStore
而不是objStore
(这应该是 VSCode 自动提示,然后我没注意按了 tab 导致的) -
浏览器支持问题
近几年还好,只要不是用 ie
目前 1.0 的话 IE 还是 partial 支持,2.0 的话 IE 是完全不支持,opera mini 也是完全不支持,除此之外的主流浏览器支持还可以。
-
缺少原生 query 语言
其他
其他一些就是跨浏览器支持不太好,比如说:
-
web sql,目前 chrome 和 Safari 还是完全不支持
-
private state tokens 是 chrome 自己提出来的实现
-
cache storage 与 service worker 有关,暂时不会涉及到这一部分
-
shared storage 应该也是 chrome 特有的,我在火狐上没看到
reference
- Using HTTP cookies