浏览器数据存储
浏览器的本地存储主要分为 Cookie、WebStorage 和 IndexedDB,其中 WebStorage 又分为 localStorage(本地存储)和 sessionStorage(会话存储)
cookie
cookie 最开始并不是用于本地存储的,而是为了弥补 HTTP 在状态管理上的不足: HTTP 是一个无状态的协议,客户端向服务器发送请求,服务器返回响应,但是下一次发送请求时服务端就无法识别客户端的身份信息,故而产生了 cookie。
cookie 本质上是浏览器里面存储的一个很小的文本文件,内部以键值对的方式存储。向同一个域名下发送请求都会携带相同的 cookie ,服务器拿到 cookie 进行解析,就能拿到客户端的状态。也就是说,cookie 的作用就是用来做状态存储的。
cookie 的实现过程:
当用户访问
web服务器后,web服务器会获取用户的状态并且返回一些数据(cookie)给浏览器,浏览器会自动储存这些数据(cookie)。当用户再次访问
web服务器,浏览器会把cookie放到请求报文中发送给web服务器,web服务器就会获取到了用户的状态。基于这次用户的状态方便用户进行其他业务的访问,并且
web服务器可以设置浏览器保存cookie的时间,cookie是有域名的概念,只有访问同一个域名的时候才会把之前相同域名返回的cookie携带给该web服务器。
缺陷:
容量缺陷:
cookie的体积上限只有4KB,只能用来存储少量的信息。性能缺陷:
cookie是紧跟域名的,不管域名下面的某个地址需不需要这个cookie,它都会携带上完整的cookie。这样随着请求数据的增多,很容易造成性能上的浪费。安全缺陷:由于
cookie以纯文本的形式在浏览器和服务器中传递,很容易被非法用户截获,然后进行一系列的篡改,并在cookie的有效期内重新发送给服务器。另外,在HTTPOnly为false的情况下,cookie信息能直接通过JS脚本读取。
cookie 属性:
| 属性 | 说明 |
|---|---|
| key | cookie的键(名称) |
| value | cookie的值 |
| max_age | cookie被保存的时间数,单位为秒。 |
| expires | 具体的过期时间,一个datetime对象或UNIX时间戳 |
| path | 限制cookie只在给定的路径可用,默认为整个域名下路径都可用 |
| domain | 设置cookie可用的域名,默认是当前域名,子域名需要利用通配符domain=.当前域名 |
| secure | 如果设为True,只有通过HTTPS才可以用 |
| httponly | 如果设为True,禁止客户端JavaScript获取cookie |
session(后端)
cookie 和 session 都是用来跟踪浏览器用户的身份的方式,有以下区别:
- 保存方式
cookie保存在浏览器端;session保存在服务器端
- 使用原理
cookie机制:如果不在浏览器中设置过期时间,cookie被保存在内存中,生命周期随浏览器的关闭而结束,这种cookie简称会话cookie。如果在浏览器中设置了cookie的过期时间,cookie被保存在硬盘中,关闭浏览器后,cookie数据仍然存在,直到过期时间结束才消失。session机制:当服务器收到请求需要创建session对象时,首先会检查客户端请求中是否包含session id。如果有session id,服务器将根据该id返回对应session对象。如果客户端请求中没有session id,服务器会创建新的session对象,并把session id在本次响应中返回给客户端。
- 存储内容
cookie只能保存字符串类型,以文本的方式。session通过类似与Hashtable的数据结构来保存,能支持任何类型的对象(session中可含有多个对象)
- 存储的大小
cookie:单个cookie保存的数据不能超过4kb。session:大小理论上没有限制。
- 安全性
cookie:针对cookie所存在的攻击:cookie欺骗,cookie截获;session的安全性大于cookie。
- 应用场景
cookie:判断用户是否登陆过网站,以便下次登录时能够实现自动登录(或者记住密码)。如果我们删除
cookie,则每次登录必须重新填写登录的相关信息。保存上次登录的时间等信息。
保存上次查看的页面
浏览计数
session:session用于保存每个用户的专用信息,变量的值保存在服务器端,通过session id来区分不同的客户。网上商城中的购物车
保存用户登录信息
将某些数据放入
session中,供同一用户的不同页面使用防止用户非法登录
- 缺点
cookie:大小受限,不能超过
4kb;用户可以操作(禁用)
cookie,使功能受限;安全性较低;
有些状态不能保存在客户端;
每次访问都要传送
cookie给服务器,浪费带宽;cookie数据有路径(path)的概念,可以限制cookie只属于某个路径下。
session:session保存的东西越多,就越占用服务器内存,对于用户在线人数较多的网站,服务器的内存压力会比较大。依赖于
cookie(session id保存在cookie),如果禁用cookie,则要使用URL重写,不安全。创建
session变量有很大的随意性,可随时调用,不需要开发者做精确地处理,所以,过度使用session变量将会导致代码不可读而且不好维护。
用法
cookie 本身没有封装 API,需要手动封装或者使用已有的库 js-cookie
// 设置cookie
const setCookie = (name: string, value: string, day: number) => {
const date = new Date()
date.setDate(date.getDate() + day)
document.cookie = `${ name }=${ value };expires=${ date }`
}
// 获取cookie
const getCookie = (name: string) => {
const entries = document.cookie.split('; ')
for (const item of entries) {
const [ key, value ] = item.split('=')
if (key === name) {
return value
}
}
}
// 移除cookie
const removeCookie = (name: string) => {
setCookie(name, '1', -1)
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Web storage API
Web Storage 存储机制是对 HTML4 中 cookie 存储机制的一个改善。由于 cookie 存储机制有很多缺点,HTML5 不再使用它,转而使用改良后的 Web Storage 存储机制。
Web Storage 提供两种类型的 API:localStorage 在本地永久性存储数据,除非显式将其删除或清空;sessionStorage 存储的数据只在会话期间有效,关闭浏览器则自动删除。
localStorage
localStorage 的存储都是字符串,如果是存储对象,那么在存储时就需要调用 JSON.stringify 方法,并且在取值时用 JSON.parse 来解析成对象。
与 cookie 的异同:
同:
- 针对一个域名,即在同一域名下,会存储同一段
localStorage。
- 针对一个域名,即在同一域名下,会存储同一段
异:
容量:
localStorage的容量上线为5MB。只存储在客户端,默认不参与与服务器端的通讯,这样就很好的避免了
cookie带来的性能和安全问题。接口封装:通过
localStorage暴露在全局,并通过它的setItem和getItem等方法进行操作。
应用场景:
- 因为
localStorage的较大容量和持久特性,可以利用localStorage存储一些内容稳定的资源;例如官网的logo,存储Base64格式的图片资源。
sessionStorage
将数据保存在 session Storage 对象中。
页面会话在浏览器打开期间一直保持,并且重新加载或恢复页面仍会保持原来的页面会话。
在新标签或窗口打开一个页面时会复制顶级浏览会话的上下文作为新会话的上下文,这点和
session cookie的运行方式不同。打开多个相同的
URL的Tabs页面,会创建各自的sessionStorage。关闭对应浏览器标签或窗口,会清除对应的
sessionStorage。
与 localStorage 的异同:
同:
容量:
sessionStorage的容量上线也为5MB。只存储在客户端,默认不参与与服务器端的通讯。
接口封装:除了名字变化,
sessionStorage的存储方式和操作方式均和localStorage一样。localStorage和sessionStorage只能存储字符串类型。
异:
sessionStorage将数据保存在Session对象中。而localStorage将数据保存在客户端本地的硬件设备,即使浏览器被关闭了该数据依然存在,下次打开浏览器访问网站时可以继续使用。localStorage的生命周期是永久的,sessionStorage的生命周期是在仅在当前会话下有效。
应用场景:
可以使用
sessionStorage对表单进行维护,将表单信息存储在里面,即使刷新表单也能保证不会让之前的表单信息丢失。可以使用
sessionStorage来存储本次浏览记录,即那种关闭页面就不需要的浏览记录。
用法
// 新增数据
localStorage.setItem('myCat', 'Tom');
// 获取数据
let cat = localStorage.getItem('myCat');
// 移除数据
localStorage.removeItem('myCat');
// 移除所有
localStorage.clear();2
3
4
5
6
7
8
9
10
11
indexedDB
IndexedDB 是一个基于 JavaScript 的面向对象数据库。是一个事务型数据库系统。
当数据量不大时,我们可以通过 sessionStorage 或者 localStorage 来进行存储,但是当数据量较大,或符合一定的规范时,我们可以使用 indexedDB 数据库来进行数据的存储,indexedDB 数据库存储理论上没有大小的限制。
IndexedDB 使用流程:
创建数据仓库(
db.createObjectStore())创建一个事务,链接数据仓库(
db.transaction([storeName]).objectStore(storeName))对数据增删改查(
stroe.add()、store.put()、store.delete()、store.get())关闭数据库(
db.close())
缺点:
indexedDB属于非关系型数据库,操作繁琐,对新手不友好
用法
interface MsgProps {
type: string,
message: string | object
}
export default class DB {
dbName: string
db: any
constructor(name: string) {
this.dbName = name
}
open(storeName: string, keyPath: string, indexs?: Array<string>) {
if (!window.indexedDB) {
return alert('您的浏览器不支持该app,为了更好的体验,请使用新版chrome浏览器')
}
const request = window.indexedDB.open(this.dbName, new Date().getTime())
return new Promise((resolve, rej) => {
request.onerror = (e: any) => {
this.onmessage({ type: '数据库连接失败', message: e })
rej()
}
request.onsuccess = (e: any) => {
this.db = e.target.result
this.onmessage({ type: '数据库连接成功', message: '' })
resolve()
}
request.onupgradeneeded = (e: any) => {
const db = e.target.result
if (!db.objectStoreNames.contains(storeName)) {
const store = db.createObjectStore(storeName, { autoIncrement: true, keyPath })
if (indexs && indexs.length) {
indexs.map((v) => {
store.createIndex(v, v, { unique: false })
})
}
store.transaction.oncomplete = (ev: any) => {
this.onmessage({ type: '创建对象仓库成功', message: '' })
}
}
}
})
}
onmessage(msg: MsgProps) {
console.log(msg.type, msg.message)
}
update(storeName: string, data: object) {
const store = this.db.transaction([ storeName ], 'readwrite').objectStore(storeName)
return new Promise((resolve, rej) => {
const request = store.put({
...data,
lastModify: new Date().getTime(),
})
request.onsuccess = (e: any) => {
this.onmessage({
type: '更新成功',
message: data,
})
resolve()
}
request.onerror = (e: any) => {
this.onmessage({
type: '更新失败',
message: data,
})
rej()
}
})
}
getList(storeName: string) {
const store = this.db.transaction([ storeName ]).objectStore(storeName)
return new Promise((resolve, rej) => {
store.getAll().onsuccess = (e: any) => {
const res = e.target.result
this.onmessage({
type: '获取列表成功',
message: res,
})
resolve(res)
}
})
}
getItem(storeName: string, key: string) {
const store = this.db.transaction(storeName).objectStore(storeName)
return new Promise((resolve, rej) => {
const request = store.get(key)
request.onsuccess = (e: any) => {
resolve(e.target.result)
}
request.onerror = (e: any) => {
rej()
}
})
}
delete(storeName: string, key: string) {
const store = this.db.transaction([ storeName ], 'readwrite').objectStore(storeName)
const request = store.delete(key)
return new Promise((resolve, rej) => {
request.onsuccess = (e: any) => {
this.onmessage({
type: '删除成功',
message: key,
})
resolve()
}
request.onerror = (e: any) => {
this.onmessage({
type: '删除失败',
message: data,
})
rej()
}
})
}
closeDB() {
this.db.close()
}
deleteDB() {
indexedDB.deleteDatabase(this.dbName)
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132