本地存储方式小结

浏览器最原始的本地存储方式是cookies,后来HTML5提供了新的客户端存储数据的方法:localStoragesessionStorage,以及数据库形式Web SQLIndexedDB

作用

cookie是最原始的本地存储方式,它起到让服务器辨识浏览器身份的作用。

由于HTTP协议是无状态的,即用户在关闭网页的时候,浏览器和服务器的连接就断开了,用户下次登录同一网页时,服务器无法辨识用户的身份。这会带来不好的用户体验。为了解决这一问题,cookie用来存储用户的身份Id,下一次访问时,浏览器会在请求头中携带cookie,这样服务器就能够通过cookie来得知用户身份,同时根据cookie来找到服务器上存储的用户的相关信息。

cookie是一段纯文本,数据以键值对的形式保存,每次浏览器发送请求都会携带cookie。由此可以想象,如果cookie存储大量的数据,会导致请求非常的缓慢,占用大量网络资源。而如果cookie只用来存储用户身份信息就比较合适。

事实上,浏览器的确对cookie的大小作了规定,不同的浏览器允许的cookie大小不同,但一般都在4kb左右。

特点

  • 不同浏览器存放cookie的位置不一样,互相不通用
  • cookie的存储是以域名的形式区分的,不同域下存储的cookie是独立的
  • 设置的cookie对当前域和它的子域都有效
  • 一个域名下存放的cookie个数是有限的,一般为20个

操作

cookie的值可以读取和设置,也可以删除。

读取
  • 浏览器可以直接在控制台中通过document.cookie读取当前域的cookie
  • 服务端如node.js可以通过request.cookies来读取(可能需要中间件解析)
设置
  • 浏览器直接在控制台中通过document.cookie="xxx=xxx"的形式来设置

    1
    2
    document.cookie="name=value";
    document.cookie="name=valueldomain=www.baidu.com";

    浏览器可以设置cookieexpires,domain,path,secure等属性

  • 服务器端可以通过responseSet-Cookie方法来设置

修改

修改cookie只需要按照设置cookie的方法重新赋值就行了。

删除

cookie的过期时间设置成一个过去的时间即可。


HTML Web Storage

HTML web storage, better than cookies.

HTML5之前,页面的数据会存储在cookie中,并在每一次服务器请求中携带发送。HTML5提出了新的本地存储方式,它们更加安全,允许更大量的数据存储到本地,但不会影响到页面性能。

不同于cookies,新的存储方式提供至少5MB的存储空间,而且存储的信息不会发送给服务器

网页存储是按源(域和协议)来区分的,所有同源的页面都能够存储和获取相同的数据。

HTML网页存储在浏览器端提供了两个用与存储数据的对象:

  • window.localStorage 用来存储不会过期的数据
  • window.sessionStorage 用来存储会话数据,即当浏览器标签页关闭时,会话数据会被清空

使用之前,我们可以先检测浏览器是否支持localStoragesessionStorage

1
2
3
4
5
6
if(typeof Storage !== "undefined"){
console.log(localStorage);
console.log(sessionStorage);
} else {
console.log("Web storage not suppoerted");
}

比如我使用之前做过的一个弹幕项目的页面来测试,可以看见localStorage里是存储了数据的:

image-20180907103004447

下面是localStorage的方法。

sessionStorage里没有数据,但同样有一些方法:

image-20180907103042541

localStorage

localStorage是HTML5的新方法,不过IE8及以上浏览器都兼容。

localStorage用来存储没有过期时间的数据,它也是以键/值对的形式存储的字符串。当浏览器关闭时,数据不会被删除,下一次打开浏览器时依然能够使用(除非手动删除数据,否则数据永不过期)。

方法
  • localStorage.setItem("name", "value"); 设置

  • localStorage.getItem("name"); 读取

    1
    2
    3
    4
    // store
    localStorage.setItem("lastname", "Smith");
    // Retrieve
    document.getElementById("result").innerHTML = localStorage.getItem("lastname");

    localStorage的值也可以和cookies一样直接设置:

    1
    2
    3
    4
    // store
    localStorage.lastname = "Smith";
    // retrieve
    document.getElementById("result").innerHTML = localStorage.lastname;
  • localStorage.removeItem("name"); 删除

    1
    localStorage.removeItem("lastname");
  • localStorage.clear() 清空

注意:

  • localStorage的存储形式是字符串,但它存储的内容不限于字符串,也可以是数组、图片、JSON、样式、脚本…(只要是能序列化成字符串的内容都可以存储)
  • 在本页面操作localStorage不会触发storage事件,但是别的页面会触发storage事件
  • IE9的localStorage不支持本地文件,需要将项目部署到服务器才能支持

sessionStorage

sessionStoragelocalStorage类似,区别在于它只用来存储会话的数据,当会话结束时(浏览器页面关闭时),数据就会被删除掉,如果页面没有关闭,此时刷新页面或进入同源的另一个页面,数据仍然存在。

需要注意的是,打开一个新的标签或窗口时会在顶级浏览上下文中初始化一个新的会话,这点和session cookies的运行机制是不一样的。

sessionStorage的使用方法和localStorage一致。


Web SQL

Web SQL是浏览器端的关系数据库,它并不是HTML5规范的一部分,而是一个独立的规范,通过SQL语句访问数据库。但在2010年W3C宣布放弃更新Web SQL。

尽管不会再更新了,但还是有一部分浏览器支持Web SQL,我们简单看一看它的使用方法,在遇到使用Web SQL的时候不至于完全不了解。

支持情况

目前,最新版的Safari, Chrome和Opeara都支持Web SQL。

核心方法

Web SQL有三大核心方法:

  • openDatabase - 创建一个新的数据库对象(可以通过使用已有数据库,也可以创建新数据库)
  • transaction - 这个方法允许我们创建一个事务,基于事务来提交或者回滚
  • executeSql - 执行实际的SQL请求

创建数据库

1
2
let db = openDatabase("mydb", "1.0", "Test DB", 2*1024*1024, fn);
// openDatabase方法对应的5个参数分别是:数据库名称、版本号、描述文本、数据库大小、回调函数

数据库被创建完毕时,第5个参数对应的回调函数会被触发,如果没有设置第五个参数,数据库也能够被创建。

执行请求

database.transaction()函数用来执行请求。这个函数接受一个函数参数,用来指明需要执行的内容:

1
2
3
4
5
let db = openDatabase("mydb", "1.0", "Test DB", 2*1024*1024);

db.transaction(function(tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS LOGS (id unique, log)');
})

上述代码会在’mydb’数据库中创建一个名叫LOGS的表。

插入数据

我们可以通过简单的SQL语句为数据库插入数据:

1
2
3
4
5
6
7
let db = openDatabase("mydb", "1.0", "Test DB", 2*1024*1024);

db.transaction(function(tx) {
tx.executeSql("CREATE TABLE IF NOT EXISTS LOGS(id unique, log)");
tx.executeSql("INSERT INTO LOGS (id, log) VALUES (1, 'foobar')");
tx.executeSql("INSERT INTO LOGS (id, log) VALUES (2, 'logmsg')");
})

我们也可以通过动态数据来操作数据库:

1
2
3
4
5
6
let db = openDatabase("mydb", "1.0", "Test DB", 2*1024*1024);

db.transaction(function(tx) {
tx.executeSql("CREATE TABLE IF NOT EXISTS LOGS(id unique, log)");
tx.executeSql("INSERT INTO LOGS (id, log) VALUES (?, ?)", [e_id, e_log]);
})

这里的e_ide_log都是外部变量,executeSql方法会遍历数组变量中的参数赋给?

读取数据

1
2
3
4
5
6
7
8
9
10
11
db.transaction(function(tx) {
tx.executeSql("SELECT * FROM LOGS", [], function(tx, results) {
let len = results.rows.length, i;
msg = "<p>Found rows: " + len + "</p>";
document.querySelector("#status").innerHTML += msg;

for(i = 0; i < len; i ++){
alert(results.rows.item(i).log)
}
}, null);
});

IndexedDB

IndexedDB是一个在用户端存储包括文件和二进制数据在内的结构化数据的低级API。它使用索引来实现高性能的数据搜索。由于网页存储对于较少量的数据存储有用,但对于更大量的结构化数据来说不太适用,IndexedDB提供了一个解决方案。

低级API VS 高级API:

没找到官方的解释为什么说IndexedDB是低级API。找到一些比较低级API和高级API的官方文章,大体意思如下:

低级API(low-level api)指原生API,高级API是通过封装低级API来的。

低级API用于管理用户自定义的硬件事件,它为有经验的程序员或工具开发者提供深度评测和控制接口。由于低级API更为底层,所以使用低级API能够提高效率。

IndexedDB与基于SQL的关系型数据库类似,它是一个事务型数据库系统。不同的是,基于SQL的关系型数据库使用固定列表,IndexedDB是基于JavaScript的基于对象的数据库,它允许用户通过一个键(key)来存储和检索对象。所有支持结构化克隆算法的对象都能够被存储到数据库中。

IndexedDB可以将数据长期存储在用户的浏览器中,而且在无网络的情况下也可以访问,所以使用IndexedDB的应用在离线状态下也可以使用。IndexedDB比较适用的场景包括:

  • 需要存储大量的数据(如图书馆租赁系统中存储DVD的目录)
  • 不需要长期连接网络的应用(如事件清单、记事本、电子邮箱客户端)

特征

  • IndexedDB数据库通过键值对存储

    IndexedDB数据库通过键值对(key-value pairs)的方式来存储数据。数据的值可以是负责的结构化对象,键可以是这些对象的属性。用户可以用对象的任意属性作为索引来实现快速查找。键也可以是二进制对象。

  • IndexedDB是事务模式的数据库

    IndexedDB数据库的所有操作都是通过事务来完成。IndexedDB API提供包括索引、表、指针等多种对象,但这些对象全都通过相应的事务来实现功能。事务有生命周期,在生命周期之后使用会报错。

  • IndexedDB API基本上是异步的

    IndexedDB不通过return语句返回数据,而是需要提供回调函数来接受数据。执行API时,向数据库发送操作请求,操作完成时,数据库会以DOM事件的方式通知用户。

  • “请求”无处不在

    每一个请求都包含onsuccessonerror事件属性。

  • IndexedDB是面向对象的

操作

初始化数据库

IndexedDB在不同的浏览器中需要添加前缀,可以用以下代码来实现统一。

1
2
3
4
5
6
window.indexdDB = window.indexdDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;

if('webkitIndexedDB' in window) {
window.IDBTransaction = window.webkitIDBTransaction;
window.IDBKeyRange = window.webkitIDBKeyRange;
}

但使用前缀的方法还是会造成一些BUG,或者功能实现不完整,这样会造成一些不太好的用户体验。还是建议明确告知客户有些浏览器并不支持IndexedDB。

1
2
3
if(!window.indexedDB) {
window.alert("Your browser doesn't support a stable version of IndexedDB.");
}
打开数据库
1
let request = window.indexedDB.open("mydb", 3);

IndexedDB使用数据库是通过请求来实现的,它并不马上打开数据库或者马上开始一个事务,发送打开请求会返回一个IDBOpenDBRequest对象,对象包含了请求成功的result值和请求失败的error值。IndexedDB中的大部分异步函数都以这样的机制运行——返回一个包含成功和错误值IDBRequest对象。open函数返回的结果是一个IDBDatabase实例。

请求中传入的第二个参数是数据库的版本。数据库的版本决定了数据库的架构(包括数据库中存储的对象和它们的结构)。

生成处理函数
1
2
3
4
5
6
7
8
let db;
let request = indexedDB.open("mydb");
request.onerror = function(e) {
console.log("Database error: " + e.target.errorCode);
};
request.onsuccess = function(e) {
db = request.result;
}
构建数据库
1
2
3
4
5
6
7
8
9
request.onupgradeneeded = function(e) {
let db = e.target.result;
let objectStore = db.createObjectStore("customers", {keyPath: 'ssn'});
objectStore.createIndex("name", "name", {unique: false});
objectStore.createIndex("email", "email", {unique:true});
for(let i in customerData) {
objectStore.add(customerData[i]);
}
}

IndexedDB没有表的概念,而是objectStore,一个数据库可以包含多个objectStore,objectStore是一个灵活的数据结构,可以存放多种类型的数据。

我们可以使用每条记录中的某个指定字段作为键值(keyPath),也可以使用自动生成的递增数字作为键值(keyGenerator),也可以不指定。

选择键的类型不同,objectStore可以存储的数据结构也有差异。

增加数据
1
let transaction = db.transaction(["customers"], "readwirte");
删除数据
1
let request = db.objectStore("customers").delete(value);

参考:

W3C: HTML5 Web Storage

MDN: window.localStorage

MDN: window.sessionStorage

很全很全的前端本地存储讲解

Migrating your WebSQL DB to IndexedDB

HTML5 - Web SQL Database

MDN: IndexedDB API

PAPIC:Low Level