浏览器存储

在开发过程中,有些数据我们需要多次用到,但是如果每一次需要就去和服务器请求,那么无疑会造成一定的资源浪费,所以我们可以根据业务需求,把一些数据在浏览器中存储起来,目前最常见的存储方案是:

  1. Cookie
  2. web存储(localStorage和sessionStorage)
  3. IndexedDB

背景

因为HTTP协议是无状态的,即服务器不知道用户上一次做了什么,这严重阻碍了交互式Web应用程序的实现。在典型的网上购物场景中,用户浏览了几个页面,买了一盒饼干和两瓶饮料。最后结帐时,由于HTTP的无状态性,不通过额外的手段,服务器并不知道用户到底买了什么,所以Cookie就是用来绕开HTTP的无状态性的“额外手段”之一。服务器可以设置或读取Cookies中包含信息,借此维护用户跟服务器会话中的状态。

Cookie的又称是HTTP Cookie,最初是在客户端用于存储会话信息,从底层来看,它作为HTTP协议的一种扩展实现,Cookie数据会自动在web浏览器和web服务器之间传输,因此在服务器端脚本就可以读写存储的cookie的值,因此Cookie通常用于存储一些通用的数据,比如用户的登陆状态,首选项等。虽然随着时代的进步,HTML5所提供的web存储机制已经逐步替代了Cookie,但有些较为老的浏览器还是不兼容web存储机制,因此正处于这个老旧更替阶段的我们对于它还是要了解了解的。

缺陷

  1. Cookie会被附加在每个HTTP请求中,所以无形中增加了流量。
  2. 由于在HTTP请求中的Cookie是明文传递的,所以安全性成问题,除非用超文本传输安全协定。
  3. Cookie的大小限制在4KB左右,对于复杂的存储需求来说是不够用的
  4. 只能储存字符串
  5. 由于第三方Cookie的滥用,所以很多老司机在浏览网页时会禁用Cookie,所以我们不得不测试用户是否支持Cookie,这也是很麻烦的一件事

优点

首先由于操作Cookie的API很早就已经定义和实现了,因此相比于其他的数据存储方式,Cookie的兼容性非常的好,兼容现在市面上所有的主流浏览器,我们在使用它的时候完全不用担心兼容问题。

web存储

web存储机制最初作为HTML5的一部分被定义成API的形式,但又由于其本身的独特性与其他的一些原因而剥离了出来,成为独立的一个标准。web存储标准的API包括locaStorage对象和seesionStorage对象。它所产生的主要原因主要出于以下两个原因:

  • 人们希望有一种在cookie之外存储回话数据的途径。
  • 人们希望有一种存储大量可以跨会话存在的数据的机制。

localStorage

localStorage对象在修订过的HTML5规范中作为持久保存客户端数据的方案取代了我们上面所提到的globalStorage。从功能上来讲,我们可以通过locaStorage在浏览器端存储键值对数据,它相比于cookie而言,提供了更为直观的API,且在安全上相对好一点 ,而且虽然localStorage只能存储字符串,但它也可以存储字符串化的JSON数据,因此相比于cookie,localStorage能存储更复杂的数据。总的来说相较于cookie,localStorage有以下优势:

  • 提供了简单明了的API来进行操作
  • 更加安全
  • 可储存的数据量更大

也正是出于以上这些原因,localStorage被视为替代cookie的解决方案,但还是要注意不要在localStorage中存储敏感信息。

localStorage的基本操作很简单,示例如下:

1
2
3
4
5
6
7
8
9
10
// 使用方法存储数据
localStorage.setItem("name", "Srtian")
// 使用属性存储数据
localStorage.say = "Hello world"
// 使用方法读取数据
const name = localStorage.getItem("name")
// 使用属性读取数据
const say = localStorage.say
// 删除数据
localStorage.removeItem("name")

但需要注意的是,我们上面的示例全是存储字符串格式的数据,当我们需要传输其他格式的数据时,我们就需要将这些数据全部转换为字符串格式,然后再进行存储:

1
2
const user = {name:"Srtian", age: 22}
localStorage.setItem("user", JSON.stringify(user))

当然,我们在获取值的时候也别忘了将其转化回来:

1
var age = JSON.parse(localStorage.user)

localStorage储存数据的有效期与作用域

通过localStorage存储的数据时永久性的,除非我们使用removeItem来删除或者用户通过设置浏览器配置来删除,负责数据会一直保留在用户的电脑上,永不过期。

localStorage的作用域限定在文档源级别的(意思就是同源的才能共享),同源的文档间会共享localStorage的数据,他们可以互相读取对方的数据,甚至有时会覆盖对方的数据。当然,localStorage的作用域同样也受浏览器的限制。

localStorage的兼容

localStorage的兼容如下表所示:

1
2
3
    Feature 	Chrome 	Edge 	Firefox (Gecko) Internet Explorer 	Opera 	Safari (WebKit)
localStorage 4 (Yes) 3.5 8 10.50 4
sessionStorage 5 (Yes) 2 8 10.50 4

sessionStorage

sessionStorage是web存储机制的另一大对象,sessionStorage 属性允许我们去访问一个 session Storage 对象。它与 localStorage 相似,不同之处在于 localStorage里面存储的数据没有过期时间设置,而Session Storage只存储当前会话页的数据,且只有当用户关闭当前会话页或浏览器时,数据才会被清除。

sessionStorage的基本语法

我们可以通过下面的语法,来保存,获取,删除数据,大体语法与:

1
2
3
4
5
6
7
8
9
10
11
12
// 保存数据到sessionStorage
sessionStorage.setItem('name', 'Srtian');

// 从sessionStorage获取数据
var data = sessionStorage.getItem('name');

// 从sessionStorage删除保存的数据
sessionStorage.removeItem('name');

// 从sessionStorage删除所有保存的数据
sessionStorage.clear();

下面的示例会自动保存一个文本输入框的内容,如果浏览器因偶然因素被刷新了,文本输入框里面的内容会被恢复,写入的内容不会丢失:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 获取文本输入框
var field = document.getElementById("field")

// 检测是否存在 autosave 键值
// (这个会在页面偶然被刷新的情况下存在)
if (sessionStorage.getItem("autosave")) {
// 恢复文本输入框的内容
field.value = sessionStorage.getItem("autosave")
}
// 监听文本输入框的 change 事件
field.addEventListener("change", function() {
// 保存结果到 sessionStorage 对象中
sessionStorage.setItem("autosave", field.value)
})
复制代码

在兼容性和优点方面,sessionStorage和localStorage是差不多的。

IndexedDB

虽然web存储机制对于存储较少量的数据非常便捷好用,但对于存储更大量的结构化数据来说,这种方法就不太满足开发者们的需求了。IndexedDB就是为了应对这个需求而产生的,它是由HTML5所提供的一种本地存储,用于在浏览器中储存较大数据结构的 Web API,并提供索引功能以实现高性能查找。它一般用于保存大量用户数据并要求数据之间有搜索需要的场景,当网络断开时,用户就可以做一些离线的操作。它较之SQL更为方便,不需要写一些特定的语法对数据进行操作,数据格式是JSON。

indexedDB的基本语法

indexedDB在浏览器存储数据相比于上面的方法更加复杂

首先,需要创建数据库,并指定数据库的版本号

1
2
//版本号得是整数
const request = IndexedDB.open(databasename, version);

然后我们需要生成处理函数,需要注意的是onupgradeneeded是我们唯一可以修改数据库结构的地方。在这里面,我们可以创建和删除对象存储空间以及构建和删除索引。

1
2
3
4
5
6
7
8
9
request.onerror = function() {
//创建数据库失败时的回调函数
}
request.onsuccess = function() {
//创建数据库成功时的回调函数
}
request.onuogradeneededd = function(e) {
//当数据库改变时的回调
}

来个实例

1
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
class IndexedDB{
constructor(dbName, storeName, version){
this.storeName = storeName;
const indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
const request = indexedDB.open(dbName, version);

request.onsuccess = e => {
this.db = e.target.result;
console.log('Init indexedDB successfully');
};
request.onupgradeneeded = e => {
this.db = e.target.result;
if(!this.db.objectStoreNames.contains(storeName)){
this.store = this.db.createObjectStore(storeName);
}
console.log('DB version changed, db version: ', this.db.version);
};
request.onerror = e => {console.info('Can not open indexedDB', e);};
}
get(key, callback){
const transaction = this.db.transaction(this.storeName);
const objectStore = transaction.objectStore(this.storeName);
const request = objectStore.get(key);

request.onerror = e => {console.info('Can not get value', e);};
request.onsuccess = e => {callback(e.target.result);};
}
set(key,value){
let oldValue;
this.get(key, function(res){oldValue = res;});

if(oldValue){
console.info('You should use function update');
}else{
const transaction = this.db.transaction(this.storeName, 'readwrite');
const objectStore = transaction.objectStore(this.storeName);
const request = objectStore.add(value, key);

request.onerror = e => {console.info('Can not add value', e);};
}
}
update(key,newValue){
const transaction = this.db.transaction(this.storeName, 'readwrite');
const objectStore = transaction.objectStore(this.storeName);
const request = objectStore.put(key,newValue);

request.onerror = e => {console.info('Can not update value', e);};
// }
}
remove(key){
const request = this.db.transaction(this.storeName, 'readwrite')
.objectStore(this.storeName)
.delete(key);
request.onerror = e => {console.info('Can not remove value', e);};
}
close(){
this.db.close();
}
}
let test = new IndexedDB('larmy','person','5880');
function callback(e) {
console.log(e)
}
function* testDB(){
yield test.set('person1','ljm');
yield test.get('person1',callback)//ljm
yield test.update('person1','LJM');
yield test.get('person1',callback)//LJM
yield test.remove('person1');
yield test.get('person1',callback)//undefined
return test.close();
}
let turn = testDB();

IndexedDB的优点(相较于前面的存储方案)

  • 拥有更大的储存空间
  • 能够处理更为复杂和结构化的数据
  • 拥有更多的交互控制
  • 每个’database’中可以拥有多个’database’和’table’

IndexedDB的局限性

了解了IndexedDB的优点,我们当然也要来聊一聊IndexedDB的局限性与适用的场景:

1. 存储空间限制

一个单独的数据库项目的大小没有限制。然而可能会限制每个 IndexedDB 数据库的大小。这个限制(以及用户界面对它进行断言的方式)在各个浏览器上也可能有所不同:

  • Firefox: 对 IndexedDB 数据库的大小没有限制。在用户界面上只会针对存储超过 50 MB 大小的 BLOB(二进制大对象)请求权限。这个大小的限额可以通过 dom.indexedDB.warningQuota 首选项进行自定义。(定义在 mxr.mozilla.org/mozilla-cen…)。
  • Google Chrome:developers.google.com/chrome…ra…

2. 兼容性问题

图片.png

从上面的图我们可以看出对于IndexedDB的兼容来讲比前面所提及的存储方案要差不少,因此在使用IndexedDB时,我们也要好好的考虑兼容性的问题

3. indexedDB受同源策略的限制

indexedDB使用同源原则,这意味着它把存储空间绑定到了创建它的站点的源(典型情况下,就是站点的域或是子域),所以它不能被任何其他源访问。要着重指出的一点是 IndexedDB 不适用于从另一个站点加载进框架的内容 (不管是 还是 。这是一项安全措施。详情请看这个:bugzilla.mozilla.org/show_bug.cg…

除此之外,IndexedDB还存在诸如:不适合存储敏感数据,相较于web存储机制的操作更加复杂等问题,这都是我们在使用IndexedDB时需要考虑的。