浏览器组成
- 用户界面
- 浏览器引擎
- 渲染引擎
- 网络
- js解释器
- 用户界面后端
- 数据存储(data storage)
浏览器离线存储的方式如下
- Cookie
- Web Storage
- WebSQL
- IndexedDB
- File System
WebSQL
关系型数据库,大多数浏览器都支持,但是现在已经不再维护了,慎用。 WebSQL的三个核心方法:
- openDatabase:使用现有数据库或创建一个新的数据库对象
- transaction:这个方法让我们执行一个事务,以及基于这种情况执行提交或回滚。
- executeSql:这个方法用于执行实际的SQL查询。
打开数据库
打开一个名为mysql的数据库,因为第一次不存在此库,所以会创建该数据库,版本号为1.0,大小为2M。
var db = openDatabase('mysql','1.0','tetst db',2*1024*1024);
执行操作
创建logs表,包含id和log字段,id是唯一的。
db.transaction(function(tx){
tx.executeSql('CREATE TABLE IF NOT EXISTS LOGS (id unique,log)');
});
执行sql
var name
var age = 15;
var db = openDatabase('mysql', 'test db', 1.0, 2 * 1024 * 1024);
// 插入操作
db.transaction(function (tx) {
// tx 是一个SQLTransaction对象,后面操作都基于tx
tx.executeSql('CREATE TABLE IF NOT EXISTS STU(id unique,name,age)');
tx.executeSql('INSERT INTO STU (id,name,age) VALUES (1,"ming",12)');
tx.executeSql('INSERT INTO STU (id,name,age) VALUES (2,"hong",13)');
tx.executeSql('INSERT INTO STU (id,name,age) VALUES (3,?,?)',[name,age]);
});
// 读取操作
db.transaction(function(tx){
tx.executeSql('SELECT * FROM STU',[],function(tx,result){
var len = result.rows.length;
for(let i=0;i<len;i++){
console.log(result.rows[i]);
}
},null);
});
// 更新操作
db.transaction(function(tx){
tx.executeSql('UPDATE STU SET name="王羲之" where id=3');
});
// 删除操作
var id = 1;
db.transaction(function(tx){
tx.executeSql('DELETE FROM STU where id=?',[id]);
});
IndexDB
简介
-
诞生的背景 浏览器的功能越来越强大,就开始考虑是否可以将大量数据存储在客户端,减少从服务端获取数据,直接从本地获取。 目前所有的数据存储方案都不适合存储大量数据,Cookie大大小不超过4KB,且每次请求都会发送回服务器;LocalStorage在2.5MB到10MB之间(各个浏览器不同),而且不提供搜索功能,不能建立自定义索引。所以提出来一种新的方案,就是IndexDB。
-
什么是IndexDB IndexDB是一种底层API,用于在客户端存储大量的结构化数据(也包括文件、二进制大型对象blobs)。该API使用索引实现对数据的高性能搜索。虽然Web Storage在存储较少量的数据很有用,但是对于存储更大量的结构化数据来说力不从心。而IndexDB提供了这种场景的解决方案。
-
通俗理解 IndexDB就是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexDB允许存储大量数据,提供查找接口,还能建立索引。这些都是LocalStorage所不具备的。就数据库类型而言,IndexDB不属于关系型数据库(不支持SQL查询语句),更接近NoSQL数据库。
-
几种常见客户端存储方式的对比:
| 属性 | 会话期Cookie | 持久性Cookie | SessionStorage | LocalStorage | IndexDB | WebSQL |
|---|---|---|---|---|---|---|
| 存储大小 | 4KB | 4KB | 2.5-10MB | 2.5-10MB | >250mb | 已废弃 |
| 失效时间 | 浏览器关闭自动清除 | 设置过期时间,到期自动清除 | 浏览器关闭后清除 | 永久保存-只能手动清除 | 手动更新或删除 | 已废弃 |
IndexDB特点:
- 键值对存储,IndexDB内部采用对象仓库存放数据。所有类型都可存入。如js对象等。主键是唯一的。
- 异步的,indexdb操作时不会锁死浏览器,用户依然可以进行其他操作。这与localStorage形成鲜明对比。后者是同步的,异步是为了放置大量数据的读写拖慢网页的表现。
- 支持事务,一系列的操作步骤之中,如果有一个操作失败,则整个事务就取消,并且数据库回滚到事务发生之前。和MySQL类似。
- 同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能跨域访问数据库。
- 存储空间大,大于250MB,没有上限。
- 支持二进制存储,可以存储字符串,也能存储二进制数据。
- 数据可视化界面,大量数据,每次请求会消耗很大性能。
- 即时聊天工具,大量消息需要存在本地
- 其他存储方式存出来不足时,不得已使用
重要概念
IndexDB是一个比较复杂的API,包含:
-
数据库:IDBDatabase对象 就是一系列数据的容器,一个域名下可以创建多个(协议+域名+端口)
-
对象仓库:IDBObjectStore对象 每个数据库包含多个对象仓库,类似于关系型数据库的table。
-
索引:IDBIndex对象 不同的属性建立索引,可以加快数据检索。
-
事务:IDBTransaction对象
-
操作请求:IDBRequest对象
-
指针:IDBCursor对象
-
主键集合:IDBKeyRange对象
实操
<script>
// 1. 创建并连接数据库
function openDB(dbname, version = 1) {
return new Promise((resolve, reject) => {
var db; // 存储创建的数据库
// 打开数据库,若没有则会创建
const request = indexedDB.open(dbname, version);
// 数据库打开成功回调
request.onsuccess = function (event) {
db = event.target.result; // 存储数据库对象
console.log('数据库打开成功');
resolve(db);
};
// 数据库打开失败回调
request.onerror = function (event) {
console.log('数据库打开失败');
// reject();
}
// 数据库有更新时的回调:1. 版本号更新 2. 添加或删除表(对象仓库)
// 第一次调用时会触发这个事件
// 初始化仓库对象-数据表
request.onupgradeneeded = function (event) {
// 数据库创建或升级时会触发
console.log('onupgradeneeded')
db = event.target.result;
// 创建仓库对象-数据表
var objectStore = db.createObjectStore('stu', {
keyPath: 'stuId', //主键
autoIncrement: true, // 实现自增
})
// 创建索引,有了索引,查询速度会加快
objectStore.createIndex('stuId', 'stuId', { unique: true });
objectStore.createIndex('stuName', 'stuName', { unique: false });
objectStore.createIndex('stuAge', 'stuAge', { unique: false });
}
});
}
// 关闭数据库
function closeDB(db) {
db.close();
console.log('关闭数据库');
}
// 删除数据库
function deleteDB(dbname) {
let deleteRequest = window.indexedDB.deleteDatabase(dbname);
deleteRequest.onerror = function (event) {
console.log("删除数据库失败");
}
deleteRequest.onsuccess = function (event) {
console.log("删除数据库成功");
};
}
// 插入数据
function addData(db, storeName, data) {
// 数据库示例,仓库对象-表,数据
// 创建读写事务
var request = db.transaction(storeName, 'readwrite')
.objectStore(storeName)
.add(data);
request.onsuccess = function () {
console.log('数据写入成功');
}
request.onerror = function () {
console.log('数据写入失败');
}
}
// 更新数据
function updateData(db, storeName, data) {
return new Promise((resolve, reject) => {
var request = db
.transaction(storeName, 'readwrite') // 事务对象,默认是只读readonly
.objectStore(storeName) // 仓库对象
.put(data);
request.onsuccess = function (e) {
resolve({
status: true,
message: '数据更新成功'
})
}
request.onerror = function (e) {
console.log("数据更新失败!")
}
});
}
// 删除数据
function deleteData(db, storeName, key) {
return new Promise((resolve, reject) => {
var request = db
.transaction(storeName, 'readwrite') // 事务对象
.objectStore(storeName) // 仓库对象
.delete(key)
request.onsuccess = function (e) {
resolve({
status: true,
message: '数据删除成功'
})
}
request.onerror = function (e) {
console.log("数据删除失败!")
}
});
}
function deleteDataByCursor(db, storeName, indexName, indexValue) {
return new Promise((resolve, reject) => {
var store = db
.transaction(storeName, 'readwrite') // 事务对象
.objectStore(storeName); // 仓库对象
var request = store.index(indexName) // 索引对象
.openCursor(IDBKeyRange.only(indexValue)); // 指针对新
request.onsuccess = function (e) {
var cursor = e.target.result;
var deleteRequest;
if (cursor) {
deleteRequest = cursor.delete(); // 请求删除当前项
deleteRequest.onsuccess = function () {
console.log("游标删除记录成功");
resolve({
status: true,
message: '游标删除记录成功'
})
}
deleteRequest.onerror = function () {
console.log("游标删除记录失败");
resolve({
status: false,
message: '游标删除记录失败'
})
}
cursor.continue();
}
resolve({
status: true,
message: '数据删除成功'
})
}
request.onerror = function (e) {
console.log("数据删除失败!")
}
});
}
// 查询数据
function getDataByKey(db, storeName, key) {
return new Promise((resolve, reject) => {
var request = db.transaction([storeName])
.objectStore(storeName)
.get(key);
request.onsuccess = function () {
console.log('查询数据成功');
resolve(request.result);
}
request.onerror = function () {
console.log('查询数据失败');
}
})
}
// 查询所有数据
function getAllData(db, storeName) {
return new Promise((resolve, reject) => {
var request = db.transaction([storeName])
.objectStore(storeName)
.getAll();
request.onsuccess = function () {
console.log('查询数据成功');
resolve(request.result);
}
request.onerror = function () {
console.log('查询数据失败');
}
})
}
// 查询所有数据:通过索引和游标
function getAllDataByCursor(db, storeName) {
return new Promise((resolve, reject) => {
var list = [];
var request = db.transaction([storeName], 'readwrite')
.objectStore(storeName)
.openCursor();// 创建一个指针(游标)
request.onsuccess = function (event) { // 游标移动一次,读一条
console.log('查询数据成功');
var cursor = event.target.result;
console.log(cursor);
if (cursor) {
list.push(cursor);
cursor.continue(); // 游标移到到下一条数据
} else {
resolve(list);
}
}
request.onerror = function () {
console.log('查询数据失败');
}
})
}
// 查询所有数据:分页查询
function getDataByPage(db, storeName, indexName, indexValue, page, pageSize) {
return new Promise((resolve, reject) => {
var list = [];
var counter = 0; // 计数器
var advanced = true; // 是否跳过多少条查询
var store = db.transaction([storeName], 'readwrite').objectStore(storeName); // 仓库对象
var request = store
// .index(indexName) // 索引对象
// .openCursor(IDBKeyRange.only(indexValue)) // 按照指定值分页查询
.openCursor();// 创建游标对象,目前指向第一条
request.onsuccess = function (event) { // 游标移动一次,读一条
var cursor = event.target.result;
if (page > 1 && advanced) {
advanced = true;
cursor.advance((page - 1) * pageSize); // 跳过多少条
return;
}
if (cursor) {
list.push(cursor.value);
counter++;
if (counter < pageSize) {
cursor.continue(); // 游标移到到下一条数据
} else {
cursor = null;
resolve(list);
}
} else {
resolve(list);
}
}
request.onerror = function () {
console.log('分页查询数据失败');
}
})
}
// 根据索引来查询数据
function getDataByIndex(db, storeName, indexName, indexValue) {
return new Promise((resolve, reject) => {
var list = [];
var request = db.transaction([storeName], 'readwrite')
.objectStore(storeName)
.index(indexName)
// .get(indexValue)
.openCursor(IDBKeyRange.only(indexValue));
// IDBKeyRange.only()--指定只包含一个值,all keys = indexValue
// IDBKeyRange.lowerBound(val,false)--指定下限 all keys >= indexValue
// IDBKeyRange.upperBound(val,false)--指定上限 all keys < indexValue
// IDBKeyRange.bound(val1,val2,false)---指定上下限 all keys <= val2 && keys >=val1
request.onsuccess = function (event) {
var cursor = event.target.result;
console.log(cursor)
if (cursor) {
list.push(event.target.result);
cursor.continue();
} else {
resolve(list);
}
}
request.onerror = function (event) {
console.log('根据索引和游标查询失败');
}
})
}
// 1. 创建stu数据库
openDB('stuDB', 1).then((db) => {
// 1. 添加数据
// addData(db, 'stu', { 'stuId': 1, 'stuName': '张三', 'stuAge': 12 });
// addData(db, 'stu', { 'stuId': 2, 'stuName': '李四', 'stuAge': 15 });
// addData(db, 'stu', { 'stuId': 3, 'stuName': '王五', 'stuAge': 11 });
// addData(db, 'stu', { 'stuId': 4, 'stuName': '张三', 'stuAge': 12 });
// addData(db, 'stu', { 'stuId': 5, 'stuName': '李五', 'stuAge': 15 });
// addData(db, 'stu', { 'stuId': 6, 'stuName': '王六', 'stuAge': 11 });
// 2. 根据主键查询数据
// return getDataByKey(db, 'stu', 2);
// 3. 查询仓库对象中的所有数据
// return getAllData(db, 'stu');
// 4. 通过游标读取数据
// return getAllDataByCursor(db, 'stu');
// 5. 通过索引查询:返回满足条件的第一条数据
// return getDataByIndex(db, 'stu', 'stuName', '张三');
// 6. 分页查询
return getDataByPage(db, 'stu', '', '', 1, 2);
// 7. 更新数据
// return updateData(db, 'stu', { 'stuId': 1, 'stuName': '张三222', 'stuAge': 122 })
// 8. 删除数据
// return deleteData(db, 'stu', 6);
// 9. 通过索引删除
// return deleteDataByCursor(db, 'stu', 'stuName', '张三');
})
.then((stuInfo) => {
// console.log('用户id=2的信息', stuInfo);
console.log(stuInfo);
});
</script>
FileAPI
我们知道html的input表单控件可以用来上传文件
<input type="file" name="" id="">
但是选择文件后,只显示文件名,而不能在线查看,只能通过上传给服务器,服务器再返回来,才能查看。用户体验很差,我们无法在客户端对用户选择的文件进行验证,无法读取文件的大小,无法预览,无法判断类型。如果是多文件上传,就更复杂了。 所以H5提供的File API就能解决这些问题,改接口允许js读取本地文件,但并不能直接访问本地文件。需要依赖用户行为,比如用户在input控件上选择了某个文件或将文件拖拽到浏览器上。
<input type="file" name="" id="">
<script>
var input = document.querySelector('input');
input.onchange = function (e) {
var files = e.target.files;
console.log(files);
console.log(files[0] instanceof File);// true
/*
FileList
0:
FilelastModified: 1640659856244
lastModifiedDate: Tue Dec 28 2021 10:50:56 GMT+0800 (中国标准时间) {}
name: "20211228.png"
size: 97366
type: "image/png"
webkitRelativePath: ""
[[Prototype]]: File
length: 1
[[Prototype]]: FileList
*/
}
</script>
简介
浏览器提供了一个原生的File()构造函数,用来生成File实例对象。
new File(array,name[,options]) File()构造函数接受三个参数:
- array:一个数组,成员可以是二进制对象,或是字符串,表示文件内容。
- name:字符串,表示文件名,或文件路径
- options:配置对象,设置实例的属性,可选。{type:字符串,表示实例对象的MIME类型,默认值为空字符串,lastModified:时间戳,最后一次修改时间,Date.now().}
File对象
var file = new File(['test test'], 'd://foo.txt');
console.log(file);
/*
lastModified: 1652452151978
lastModifiedDate: Fri May 13 2022 22:29:11 GMT+0800 (中国标准时间) {}
name: "d://foo.txt"
size: 9
type: ""
webkitRelativePath: ""
[[Prototype]]: File
*/
以上代码通过构造函数的方式创建了File对象,并打印了该文件的属性信息。因为File对象没有自己的实例方法,由于继承了Blob对象,所以可以调用Blob对象的方法slice()。
FileList对象
FileList是一个类似于数组的对象,代表一组选中的文件,没一个成员都是File实例。 使用场景:
- input空间的files属性,返回FileList实例。
- 拖拽一组文件时,目标区的DataTransfer.files属性,返回FileList实例。
FileReader对象
FileReader对象用于读取File对象或Blob对象所包含的文件内容。 浏览器原生提供了FileReader构造函数,用来生成FileReader实例。
var reader = new FileReader();
该对象有以下属性:
- FileReader.error:读取文件时产生的错误对象
- FileReader.readyState:表示读取文件时的当前状态,0-未加载任何数据,1-表示正在加载数据,2-表示加载完成
- FileReader.result:读取完成后的文件内容,字符串或ArrayBuffer实例
- FileReader.onabort:用户终止读取的监听函数
- FileReader.onerror:读取错误的监听函数
- FileReader.onload:读取完成的监听函数,通常在这里使用result属性拿到文件内容
- FileReader.onloadstart:读取操作开始的监听函数
- FileReader.onloadend:读取操作结束的监听函数
- FileReader.onprogress:读取操作进行中的监听函数 该对象有以下实例方法:
- FileReader.abort():读取终止操作,readyState直接变为2
- FileReader.readAsArrayBuffer():返回ArrayBuffer实例。
- FileReader.readAsBinaryString():返回二进制字符串。
- FileReader.readAsDefaultURL():返回Data URL格式(Base64编码)的字符,不能对这个字符直接进行base64解码,需要把前缀data:image/png;base64, 从字符串去掉以后进行解码。
- FileReader.readAsText():读取完成后,result属性将返回文件内容的文本字符串。第一个参数是文件的Blob实例,第二个参数是可选的,表示文本编码,默认为UTF-8。 示例:
var input = document.querySelector('input');
var img = document.querySelector('img');
input.onchange = function (e) {
var files = e.target.files;
var firstFile = files[0];
if (firstFile.type === "text/plain") {
var reader = new FileReader();
reader.readAsText(firstFile);//异步的通过监听函数拿到内容
reader.onload = function (e) {
console.log('文件内容', e.target.result);
}
}
if (firstFile.type === "image/png") {
var reader = new FileReader();
reader.readAsDataURL(firstFile);
reader.onload = function (e) {
img.src = e.target.result;// 实现图片预览
}
}
}
File System Access API
这里才跟离线存储有关,File Systen Access API 提供了比较稳妥的本地文件交互模式。既实用又安全。 目前只有Chrome支持。
可以去MDN研究下~ (以后有空再更新...)