持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情
什么是前端Web 存储?
简单来说,就是使用HTML5可以在本地存储用户的数据。
早些时候,本地存储使用的是 cookie。但是Web 存储需要更加的安全与快速,这些数据不会被保存在服务器上,但是这些数据可用于用户请求网站的数据。它也可以存储大量的数据,而不影响网站的性能。
我总结了前端Web存储的5种方式,但是实际上可能会有很多种方式,比如使用一些第三方库来实现的前端存储,但是他们底层也是使用了这几种方式。
1.cookies
在HTML5标准前,本地储存的主要方式,优点是兼容性好,请求头自带cookie方便,缺点是大小只有4k(不知道现在有没有变化),自动请求头加入cookie浪费流量,每个domain限制20个cookie,使用起来麻烦需要自行封装,还有跨域的问题,因为之前讲过,在跨域的情况下,浏览器默认是不携带cookie信息的,我的之前文章里有讲过,需要理解的小伙伴们可以去查看。
这种方式现在已经很少使用了,只是作为了解就可以了。
2. localStorage
localStorage,是HTML5加⼊的以键值对(Key-Value)为标准的方式,键值和值只能是字符串,优点是操作⽅便,永久性储存(除非手动删除),大小为5M。使用起来也很简单,只有几个简单的API,在实际项目中使用的还是很多的。
3. sessionStorage:
与localStorage基本类似,区别是sessionStorage当页面关闭后会被清理,而且与cookie、localStorage不同,他不能在所有同源窗中共享,是会话级别的储存方式,这也是它和localStorage的区别。在实际项目中使用也是比较多的。
localStorage和localStorage使用起来都比较简单,所以这里就不在准备代码例子了。
4. Web SQL
2010年被W3C废弃的本地数据库数据存储方案,但是主流浏览器(火狐除外)都已经有了相关的实现,web SQL类似于SQLite,是真正意义上的关系型数据库,用sql进行操作,当我们使用时要进行转换,比较繁琐。关系型数据库中的表,列比较固定,而前端数据比较灵活,读取数据后还需要进行转换,所以前端的数据其实并不适合存储在关系型数据库中。
Web SQL 数据库 API 并不是 HTML5 规范的一部分,但是它是一个独立的规范,引入了一组使用 SQL 操作客户端数据库的 APIs。
虽然它不是H5的标准,但是曾经出现过,而且大部分主流浏览器已经实现了,尽管我们在工作中可能不会去使用它,但是我觉得还是有必要了解一下。先来看一下它的几个核心API吧。
1. 核心API
- openDatabase:这个方法使用现有的数据库或者新建的数据库创建一个数据库对象。
- transaction:这个方法让我们能够控制一个事务,以及基于这种情况执行提交或者回滚。
- executeSql:这个方法用于执行实际的 SQL 查询。
2. 打开数据库
var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024);
openDatabase() 方法对应的五个参数说明:
- 数据库名称
- 版本号
- 描述文本
- 数据库大小
- 创建回调
第五个参数,创建回调会在创建数据库后被调用。
3. 执行查询
var 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)');
});
4. 插入数据
var 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]);
});
5. 读取数据
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>我的教程</title>
<script type="text/javascript">
var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024);
var msg;
db.transaction(function (tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS LOGS (id unique, log)');
tx.executeSql('INSERT INTO LOGS (id, log) VALUES (1, "Vue教程")');
tx.executeSql('INSERT INTO LOGS (id, log) VALUES (2, "React教程")');
msg = '<p>数据表已创建,且插入了两条数据。</p>';
document.querySelector('#status').innerHTML = msg;
});
db.transaction(function (tx) {
tx.executeSql('SELECT * FROM LOGS', [], function (tx, results) {
var len = results.rows.length, i;
msg = "<p>查询记录条数: " + len + "</p>";
document.querySelector('#status').innerHTML += msg;
for (i = 0; i < len; i++){
msg = "<p><b>" + results.rows.item(i).log + "</b></p>";
document.querySelector('#status').innerHTML += msg;
}
}, null);
});
</script>
</head>
<body>
<div id="status" name="status">状态信息</div>
</body>
</html>
当然还可以更新和删除!
db.transaction(function (tx) {
tx.executeSql('DELETE FROM LOGS WHERE id=1'); //删除
tx.executeSql('DELETE FROM LOGS WHERE id=?', [id]);
tx.executeSql('UPDATE LOGS SET log='Angular' WHERE id=2'); //更新
tx.executeSql('UPDATE LOGS SET log='算法' WHERE id=?', [id]);
});
如何在浏览器中查看数据库数据?
6. 存储位置:
C:\Users\...\AppData\Local\Google\Chrome\User Data\Default\databases\
5. IndexedDB
背景
Cookie 的大小不超过4KB,且每次请求都会发送回服务器。localStorage 在 2.5MB 到 10MB 之间(各家浏览器不同),而且不提供搜索功能,不能建立自定义的索引,且只能保存字符串,使用起来需要转换,很不方便。所以,需要一种新的解决方案,这就是 IndexedDB 诞生的背景。
它是被正式纳入HTML5标准的数据库存储方案,它是NoSQL(Not Only SQL)数据库,用键值对进行储存,可以进行快速读取操作,非常适合web场景,同时用JavaScript进行操作会非常方便。
IndexedDB是浏览器提供的本地数据库, 允许储存大量数据,提供查找接口,还能建立索引,功能非常强大,这些都是 localStorage 所不具备的。就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。
IndexedDB的特点
键值对储存
IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以"键值对"的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。
异步
IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 localStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。简单来说,就是读取数据的时候,是异步操作,而localStorage是同步的为什么没有问题呢?只是localStorage的数据量很小,耗时很短,设计成同步的也不会有大的影响,而且使用也简单。
支持事务
IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。支持事务这也是一个优秀数据库必备的基本技能哦。
同源限制
IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。为了安全性考虑,自然是只能访问自身域下的数据。
储存空间大
IndexedDB 的储存空间比 localStorage 大得多,一般来说不少于 250MB,甚至没有上限。
支持二进制储存
IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。IndexedDB竟如此的优秀。
操作
打开数据库
使用 IndexedDB 的第一步是打开数据库,使用indexedDB.open()方法。
这个方法接受两个参数,第一个参数是字符串,表示数据库的名字。如果指定的数据库不存在,就会新建数据库。第二个参数是整数,表示数据库的版本。如果省略,打开已有数据库时,默认为当前版本;新建数据库时,默认为1。
indexedDB.open()方法返回一个 IDBRequest 对象。这个对象通过三种事件error、success、upgradeneeded,处理打开数据库的操作结果。
var db;
var objectStore;
var request = window.indexedDB.open(databaseName, version);
request.onerror = function (event) {}
request.onsuccess = function (event) {
db = request.result//可以拿到数据库对象
}
//如果指定的版本号,大于数据库的实际版本号,就会发生数据库升级事件upgradeneeded
request.onupgradeneeded = function (event) {
db = event.target.result;
if (!db.objectStoreNames.contains('person')) {//判断是否存在
objectStore = db.createObjectStore('person', { keyPath: 'id' });
//自动生成主键db.createObjectStore(
// 'person',
// { autoIncrement: true }
//);
}
//新建索引,参数索引名称、索引所在的属性、配置对象
objectStore.createIndex('email', 'email', { unique: true });
}
新增数据
在以上操作的基础上,需要新建一个事务。新建时必须指定表格名称和操作模式("只读"或"读写")。新建事务以后,通过IDBTransaction.objectStore(name)方法,拿到 IDBObjectStore 对象,再通过表格对象的add()方法,向表格写入一条记录。
写入操作是一个异步操作,通过监听连接对象的success事件和error事件,了解是否写入成功。
function add() {
var request = db.transaction(['person'], 'readwrite')
.objectStore('person')
.add({ id: 1, name: '张三', age: 24, email: 'zhangsan@example.com' });
request.onsuccess = function (event) {
console.log('数据写入成功');
};
request.onerror = function (event) {
console.log('数据写入失败');
}
}
add();
读取数据
objectStore.get()方法用于读取数据,参数是主键的值。
function read() {
var transaction = db.transaction(['person']);
var objectStore = transaction.objectStore('person');
var request = objectStore.get(1);
request.onerror = function(event) {
console.log('事务失败');
};
request.onsuccess = function( event) {
if (request.result) {
console.log('Name: ' + request.result.name);
console.log('Age: ' + request.result.age);
console.log('Email: ' + request.result.email);
} else {
console.log('未获得数据记录');
}
};
}
read();
遍历数据
遍历数据表格的所有记录,要使用指针对象 IDBCursor。openCursor()方法是一个异步操作,所以要监听success事件。
function readAll() {
var objectStore = db.transaction('person').objectStore('person');
objectStore.openCursor().onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
console.log('Id: ' + cursor.key);
console.log('Name: ' + cursor.value.name);
console.log('Age: ' + cursor.value.age);
console.log('Email: ' + cursor.value.email);
cursor.continue();
} else {
console.log('没有更多数据了!');
}
};
}
readAll();
数据更新
function update() {
var request = db.transaction(['person'], 'readwrite')
.objectStore('person')
.put({ id: 1, name: '李四', age: 35, email: 'lisi@example.com' });
request.onsuccess = function (event) {
console.log('数据更新成功');
};
request.onerror = function (event) {
console.log('数据更新失败');
}
}
update();
数据删除
IDBObjectStore.delete()方法用于删除记录。
function remove() {
var request = db.transaction(['person'], 'readwrite')
.objectStore('person')
.delete(1);
request.onsuccess = function (event) {
console.log('数据删除成功');
};
}
remove();
索引
添加索引后可以使用索引查询数据。建立索引,其实就是为了提高查询的速度。
var transaction = db.transaction(['person'], 'readonly');
var store = transaction.objectStore('person');
var index = store.index('name');
var request = index.get('李四');
request.onsuccess = function (e) {
var result = e.target.result;
if (result) {
// ...
} else {
// ...
}
}
完整案例
基于Vue2我做了一个小例子,实现了添加,删除等基本操作。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IndexedDB示例</title>
<script type="text/javascript" src="https://unpkg.com/vue"></script>
<style>
.person {
height: 25px;
padding: 5px;
}
</style>
</head>
<body>
<div id="app"></div>
<script>
// IndexedDB
var db;
var objectStore;
var request = window.indexedDB.open('MyFirm', 1);
request.onerror = function(event) {
console.error('初始化数据库发生错误!');
}
request.onsuccess = function(event) {
db = request.result //可以拿到数据库对象
}
//如果指定的版本号,大于数据库的实际版本号,就会发生数据库升级事件upgradeneeded
request.onupgradeneeded = function(event) {
db = event.target.result;
if (!db.objectStoreNames.contains('person')) { //判断是否存在
// 自动生成主键
objectStore = db.createObjectStore(
'person', {
autoIncrement: true
}
);
}
}
const app = new Vue({
el: "#app",
data() {
return {
name: '',
age: null,
selectedKey: 0,
persons: []
}
},
template: `
<div>
<p>Hello IndexedDB!</p>
<input v-model="name" type="text" placeholder="姓名"/>
<input v-model="age" type="number" placeholder="年龄"/>
<button @click="addPerson">添加</button>
<button @click="delPerson">删除</button>
<div class="person" v-for="p in persons" @click="selectedKey=p.key;" :style="{'background-color': selectedKey === p.key ? 'aqua' : 'transparent'}">
#{{p.key}} {{p.name}}: {{p.age}}
</div>
<p>总共有{{personCount}}人!</p>
</div>
`,
mounted() {
setTimeout(() => {
this.getAllPersons();
}, 1000);
},
methods: {
addPerson() {
let person = {
name: this.name
};
if (this.age) {
person.age = this.age;
}
var request = db.transaction(['person'], 'readwrite')
.objectStore('person')
.add(person);
request.onsuccess = (event) => {
console.log('添加成功!');
person.key = event.target.result;
this.persons.push(person);
};
request.onerror = function(event) {
console.log('添加失败!');
}
},
getAllPersons() {
var objectStore = db.transaction('person').objectStore('person');
objectStore.openCursor().onsuccess = (event) => {
var cursor = event.target.result;
if (cursor) {
let person = {};
person.key = cursor.key;
person.name = cursor.value.name;
person.age = cursor.value.age;
this.persons.push(person);
cursor.continue();
}
};
},
delPerson() {
if (!this.selectedKey) {
alert('请选中');
return;
}
var request = db.transaction(['person'], 'readwrite')
.objectStore('person')
.delete(this.selectedKey);
request.onsuccess = (event) => {
this.persons = this.persons.filter((p) => p.key !== this.selectedKey);
};
}
},
computed: {
personCount() {
return this.persons.length;
}
},
});
</script>
</body>
</html>
IndexDB是不能跨域共享的,每个源都有可以存储的空件限制。 IndexedDB存储的是对象,而不是数据表,因为IndexedDB不是关系型数据库。对象存储是通过定义键然后添加数据来创建。游标用于查询对象存储中特定数据,而索引可以针对特定属性实现更快的查询。
总结
Web SQL,IndexedDB VS localStorage
localStorage相比 Web SQL,和IndexedDB,容量受限,且只能存储stirng字符串,使用时需要来回转换,操作上也只能getItem,setItem等,而且是同步的。而Web SQL,和IndexedDB 存储数据不限于string字符串,可以是二进制流或Blob对象。
Web SQL VS IndexedDB
Web SQL,和IndexedDB都属于数据库,Web SQL是关系型数据库,IndexedDB是非关系型数据库。
尽管很多浏览器支持WebSQL,使用较广泛,但是WebSQL不是HTML5的标准,且已经被废弃,不建议使用。
IndexedDB是HTML5标准,由于是非关系型数据库,所以易于扩展,读取的数据无需做转换,非常适合Web的场景,建议使用。