一、背景介绍
第一个Web存储的技术叫做Cookie,它是网站的身份证。是网站为了辨别用户身份,进行session(服务端的session)跟踪而存储在用户本地终端上的数据,也就是说它是存在电脑硬盘上的,一个很小的txt类型的文件。Cookie每次都会跟随http请求发送到服务端,也就是说每一个http请求都会带上我们的cookie数据,因此它存在一个安全性的问题。
cookie本身也是有很大的局限性的,首先它很小,主流的浏览器最大支持 4096 字节,除了最大字节的限制,每个网站的cookie个数(也就是每一个first每一个域)也是有限制的,一般浏览器是20个。除此之外,cookie还会默认跟随所有http请求发送,即使不需要使用这个cookie来鉴别用户但是它也是会跟随http请求发送的,这样就会造成一个网络资源的浪费。然后部分的浏览器还限制了总的cookie个数300个。
在cookie的诸多局限性下,Web Storage应运而生。Web Storage 解决了很多问题:
比如它支持存储大量数据,支持复杂的本地数据库,而且也不会默认跟随http请求。Web Storage主要是有四个:
- SessionStorage
- LocalStorage
- WebSQL
- indexedDB
二、Cookie的简单介绍
Cookie是HTML4的一个标准,它一般不需要考虑兼容。它是网站的一个身份证,服务器可以针对不同用户,做出不同的响应。cookie存储在用户的机器上是一个纯文本,就是一个txt文件并不是一个脚本,它不能执行东西只负责记录。浏览器每次请求都会带上当前网站的cookie。
Cookie分为两种类型,一种呢是会话cookie,也就是临时性的cookie,退出浏览器或者是关闭即删除;
另一种叫持久cookie,它会一直存在,存在的时间由特定的过期时间或者是有效期来决定。
Cookie的域 Domain决定了当前的一个cookie的权限,哪一个域可以使用这个cookie。
Cookie的路径 Path,下面一个简单的例子:
www.baidu.com id="123456" domain="www.baidu.com"
www.baidu.com/user id="123456" user="eric" domain="www.baidu.com" path="/user/"
www.baidu.com/search id="123456";
www.baidu.com/user/search id="123456" user="eric";
如上www.baidu.com设置了一个id等于123456,domain是www.baidu.com,然后另外一个跟第一个一样多设置了一个user,id相同,但是多了一个user=“Eric”,它的domain设置成了www.baidu.com,path就到了user下面。这两者设置完成之后,当我们访问www.baidu.com/search时百度只能拿到id,因为user="Eric"是属于user这个域下面的,也就是说在search下面是获取不到的,但是在www.baidu.com/user/search这个时候我们就可以获取到名叫Eric的user。Path也是一种权限的控制只是相较于域domain是低一级的。
Cookie的安全secure,如果这个属性为TRUE,那么网站只有在https的请求下面才会携带当前的cookie。
Cookie的HttpOnly这个属性如果为TRUE,那么就不允许JavaScript操作cookie。
因为cookie是存储在客户端一个独立的文件,因此服务器是无法分辨用户和攻击者的。关于cookie的目的分为两种:一种是跨站点脚本攻击,一种是跨站请求伪造。
三、SessionStorage
key-value的键值对,是HTML5新增的一个会话存储对象。
SessionStorage是临时保存在同一窗口,也就是同一标签页的数据。如果当前标签页关闭了,那么SessionStorage也就失效了。这也是SessionStorage最显著的一个特点:单页标签限制。
除此之外,它还有的一些特点有:
- 同源策略,也就是在同一协议,同一主机名和同一端口下的同一tab
- 只在本地存储,不会跟随http请求发送到服务器
- 存储方式采用key-value键值对,这里面的value只能存字符串类型,如果存其他的会自动转换成字符串。
- 存储上线限制达到了5MB,如果当前存储超出上限新的内容会把旧的内容覆盖但不会报错。
属性:
- sessionStorage.length - 键值对数量
- sessionStorage.key(int index) -> null
- sessionStorage.getItem(string key) -> null
- sessionStorage[string key]
- sessionStorage.setItem(string key, string value)
- sessionStorage.removeItem(string key)
- sessionStorage.clear()
Json对象
- JSON.stringify()
- JSON.parse()
四、LocalStorage
LocalStorage也是在浏览器的Application下面有一个Local Storage,它和SessionStorage是十分相似的,同样是key-value键值对,也是HTML5的新增存储对象,它与SessionStorage的特点不同之处在于没有标签页的限制和在浏览器的无痕模式下LocalStorage是不允许读取的,永久性的存储,然后SessionStorage超出限制是覆盖不会报错而LocalStorage超出会报错。
特点:
- 同源策略,也就是在同一协议,同一主机名和同一端口下的同一tab
- 没有标签页的限制
- 只在本地存储,不会跟随http请求发送到服务器
- 存储方式采用key-value键值对,这里面的value只能存字符串类型,如果存其他的会自动转换成字符串。
- 存储上线限制达到了5MB,如果当前存储超出上限会报错。
- 无痕模式下不可读取
- 永久性存储
属性:
- sessionStorage.length - 键值对数量
- sessionStorage.key(int index) -> null
- sessionStorage.getItem(string key) -> null
- sessionStorage[string key]
- sessionStorage.setItem(string key, string value)
- sessionStorage.removeItem(string key)
- sessionStorage.clear()
注意事项:LocalStorage和SessionStorage在web view是不可靠的,web view指的是在开发混合APP的时候使用了浏览器来实现我们的APP,这个时候是不可靠的,因为在浏览器崩溃的情况下数据可能没有存进去。
另外一个在IOS浏览器中不可重复setItem,如果重复会报错,然后这个时候我们需要先removeItem再添加item。
监听storage的变化
监听storage包括SessionStorage和LocalStorage。然后这里需要提到两个概念:同源和监听同源网页。
-
同源:协议、域名、端口三者相同,同源的情况下我们可以共享SessionStorage和LocalStorage。
同源策略还禁止不同源执行任何脚本。http://localhost:63342/simpleApp/app/index.html#/ (协议) (域名) (端口) (路径)
-
监听同源网页,但是同一网页是无效的
window.addEventListener("storage", function (event) { console.log(event.key); console.log(event.oldValue); console.log(event.newValue); console.log(event.url); console.log(event.storageArea); });
(任何一个技术的产生都是为了解决一系列的问题)
五、IndexedDB
IndexedDB 背景
- Storage(Storage指的是SessionStorage和LocalStorage)不适合存储大量的数据
- Storage不能提供搜索功能
- Storage不能建立索引,存储的内容也比较少
- IndexedDB扩大了web存储的容量,可以达到250MB以上
基本概念
首先它是一个NoSQL,也就是一个非关系型数据库。MySQL和Oracle都是关系型数据库。意思就是说如果建立了两个表在关系型数据库里面我们可以通过一个外链把多个表联系起来,但是NoSQL不行,在NoSQL里面如果想要多个表关联起来,我们只能手动的在关联的表里面添加上需要关联的另外一个或多个表的id。这是NoSQL与MySQL两者之间的一个区别。
IndexedDB的特点也是和Storage是一样的:
- 键值对储存 ,但是允许所有类型,不允许主键重复报错
- 是一个异步操作, 不阻塞浏览器线程
- 支持事务,事务是SQL数据库的一个概念,也就是说我们进行任何的增删改查都要在某一个事务下面进行,提供了一个回滚功能,一系列操作有一步失败, 数据库回滚到事务发生之前的状态,这样为了避免操作中途出现失败,影响整个数据库的状态
- 同源限制
- 支持二进制储存
IndexedDB几个基本概念:
- IDBDatabase - 数据库
- IDBObjectStore - 对象仓库
- IDBIndex - 索引
- IDBTransaction - 事务
- IDBRequest - 操作请求
- IDBCursor - 指针
- IDBKeyRange - 主键集合
IndexedDB浏览器兼容
IDBDatabase
IDB是IndexedDB的缩写,它呢就是数据库,数据库也叫作数据的一个容器。每一个源(同源策略)可以建立很多数据库。Database有一个版本的概念,版本对应着数据库表,同一时刻只能存在一个版本。比如:新增一个表,然后我们需要把database的版本加一,表里面要新增一列,这时同样需要把数据库版本加一。
注意:1 同一时刻只能有一个版本存在 2 修改数据库结构只能通过升级数据库版本
-
打开数据库
/* databaseName不存在则创建 */ /* version为整数, 新建时为1 */ let database; let userStore; const request = window.indexedDB.open(databaseName, version); /* 成功打开数据库 */ request.onsuccess = event => { database = request.result; } /* 打开数据库失败 */ request.onerror = error => { console.log(error); } /* 版本号大于当前数据库版本 */ request.onupgradeneeded = event => { database = event.target.result; }
注意:如果在打开数据库时,数据库不存在,将会新建一个
IDBObjectStore(数据库表)
创建表,最好是在upgradeneeded下执行;在创建数据库表的时候需要指定主键,主键代表了唯一的标识,比如 keyPath:‘id’;如果不指定主键,我们可以指定一个autoIncrement:true,自增的一个概念,也就是不指定主键数据库会自动添加主键而且这个主键就是数字,依次递增的。
const createStore = () => {
//如果当前的objectStoreNames.contains包含user,如果不包含user这个表,然后就用这个database.createObjectStore创建了一个表,这个表的名字就叫做user,然后主键就是下面的id
if(!db.objectStoreNames.contains('user')) {
userStore = database.createObjectStore('user', { keyPath: 'id' });
}
}
指定索引:
const createStore = () => {
if(!database.objectStoreNames.contains('user')) {
userStore = database.createObjectStore('user', { keyPath: 'id' });
userStore.createIndex('name', 'name', { unique: true });
}
}
IDBTransaction(事务)
创建完之后需要往里面添加数据,添加数据我们就需要使用到事务。
事务涉及到数据库的增删改查,它有三个状态:
- complete
- error
- abort
属性:
- IDBTransaction.db 当前数据库
- IDBTransaction.mode 模式,使用模式分为readonly和readwrite
- IDBTransaction.objectStoreNames 当前数据库涉及到的哪几个数组表
- IDBTransaction.error 回调
数据库的基本操作:增删改查以及清空。
新增数据(add)
分为两种情况:一种是使用自增的数据库的id或者是自增的一个键值,如果已经创建主键,那么新增必须包含主键和另一种已创建主键但主键不可重复。
const add = () => {
/* 创建事务 */
/* 使用某个数据库 */
/* add新增 */
transactionRequest = database.transaction(['user'], 'readwrite')
.objectStore('user')
.add({ id: 100, name: 'Eric', age: 28, email: '[email protected]' });
/* 成功 */
transactionRequest.onsuccess = event => {
console.log('数据写入成功', event);
};
/* 失败 */
transactionRequest.onerror = error => {
console.log('数据写入失败', error);
}
}
读取数据(get)
const read = () => {
/* 创建事务 */
transaction = database.transaction(['user']);
/* 选择数据库表 */
table = transaction.objectStore('user');
/* 读取数据 */
transactionRequest = table.get(2);
/* 成功 */
transactionRequest.onerror = event => {
console.log('数据读取失败', event);
};
/* 失败 */
transactionRequest.onsuccess = event => {
if (transactionRequest.result) {
console.log('数据读取成功', transactionRequest.result);
} else {
console.log('未读取到数据');
}
};
}
更新数据(put)
更新不存在的数据时会新建,也就是说在新增数据时如果相同,往往会出错,但是在更新数据时不会出错。如果数据不存在就会新建,如果存在就会一直更新。
const update = () => {
transactionRequest = database.transaction(['user'], 'readwrite')
.objectStore('user')
.put({ id: count, name: 'David', age: 35, email: '[email protected]' });
transactionRequest.onsuccess = function (event) {
console.log('更新数据成功', event);
};
transactionRequest.onerror = error => {
console.log('更新数据失败', error);
}
}
删除数据(delete)
const delete = () => {
transactionRequest = database.transaction(['user'], 'readwrite')
.objectStore('user')
.delete(2);
transactionRequest.onsuccess = function (event) {
console.log('删除数据成功', event);
};
transactionRequest.onerror = error => {
console.log('删除数据失败', error);
}
}
清空数据(clear)
IDBCursor(指针)
提供了一种遍历数据的可能。
const readAll = () => {
table = database.transaction('user').objectStore('user');
table.openCursor().onsuccess = () => {
let cursor = event.target.result;
if (cursor) {
console.log('数据遍历', cursor);
cursor.continue();
} else {
console.log('数据遍历完成');
}
};
}
关闭IndexedDB数据库连接
const closeDataBase = () => {
database.close();
}
删除IndexedDB数据库前,须先关闭数据库连接
const deleteDataBase = () => {
indexedDB.deleteDatabase('first_database');
}
六、WebSQL
基本概念:并不是 HTML5 的规范 , 只能算是一个独立的规范;使用WebSQL是完完全全的SQL 语句,使用SQL语句来操作客户端数据库;它一共有三个比较重要的概念,分别是:openDatabase 打开数据库,可以是使用现有数据库或者新建数据库;transaction 事务,所有的数据库都支持事务;executeSql 执行SQL语句。
openDatabase(打开数据库)
相比于IndexedDB的概念稍微多一点,主要是有数据库名称、版本号(在IndexedDB里面版本号都是整数,但是在WebSQL里面它可以是小数)、描述文本(介绍数据库是干什么的)、数据库大小和创建回调(function,只在第一次创建的时候才会调用)。
const database = openDatabase('my_database', '1.0', 'first', 2 * 1024 * 1024, function() {
});
Transaction(事务)
-
创建表
const createTable = () => { database.transaction(function (content) { content.executeSql('CREATE TABLE IF NOT EXISTS USER (id unique, name)'); }); }
-
添加数据
const addData = () => { database.transaction(function (content) { content.executeSql('INSERT INTO USER (id, name) VALUES (1, "Eric")'); }); }
-
查询数据
const searchData = () => { database.transaction(function (content) { content.executeSql('SELECT * FROM USER'); }); }
-
更新数据
const updateData = () => { database.transaction(function (content) { content.executeSql('UPDATE USER SET name=\'David\' WHERE id=1'); }); }
-
删除数据库表
const deleteDataBase = () => { database.transaction(function (content) { content.executeSql('DROP TABLE USER'); }); }