方案简介
| 名称 | 同步/异步 | 大小限制 | 生命周期 | 支持度 | 同源策略 |
|---|---|---|---|---|---|
| sessionStorage | 同步 | 4kb | 临时性 | 良好 | 遵循 |
| localStorage | 同步 | 5m左右 | 永久存储 | 良好 | 遵循 |
| Indexed | 异步promise | 2GB 左右 | 永久存储 | 一般 | 遵循 |
| WebSql | 异步promise | 10MB左右 | 永久存储 | 差 | 遵循 |
| Cookie | 同步 | 4kb | 临时性 | 良好 | 遵循 |
| localforage | 异步promise | 2GB 左右 | 永久存储 | 一般 | 遵循 |
Tips:
- 同步会阻塞JavaScript主线程!,异步读取并不会干扰主线程的运行。
- 遵循同源策略,这意味着它会受到同源策略的限制。换句话说,这些API只能在同一源(即协议、主机和端口都相同)的网页之间共享数据。如果你的网页试图访问不同源的数据,浏览器会阻止这种访问并抛出安全性错误,一般情况下,不同源之间也无法直接相互访问
1. sessionStorage
- 读取数据:
sessionStorage.getItem (key) - 删除键名对应的数据:
sessionStorage.removeItem(key) - 清空数据记录
sessionStorage.clear() - 存储数据:
sessionStorage.setItem(key,value)
2. localStorage
- 读取数据:
localStorage.getItem (key) - 删除键名对应的数据:
localStorage.removeItem(key) - 清空数据记录
localStorage.clear() - 存储数据:
localStorage.setItem(key,value)
3. Indexed
支持度
支持事务IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
类似于Mysql,内部数据记录是用键值对的形式存储,每一个列都有对应列名,一张表内至少有一个主键/索引来供给查询用
支持二进制储存IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象
该数据库操作过于繁杂,接下来用代码以及注释的形式来介绍整个数据库的增删改查操作
数据库初始化
/**
* 第一次打开数据库,确定是否有对应的数据库,没有则创建,创建后检测是否有name表,没有则创建//name表的长度和chats表数量要一模一样
* @param {object} dbName 数据库的名字
* @param {string} storeName 仓库名称
* @param {string} version 数据库的版本
* @return {object} 该函数会返回一个数据库实例
*/
export function openDB(StoreName :any, version = 1) {
return new Promise((resolve, reject) => {
// 兼容浏览器
const win: any = window;
var indexedDB: any =
win.indexedDB ||
win.mozIndexedDB ||
win.webkitIndexedDB ||
win.msIndexedDB;
let db;
// 打开数据库,若没有则会创建
const request = indexedDB.open(StoreName, version);
// 数据库打开成功回调
request.onsuccess = function (event :any) {
db = event.target.result; // 数据库对象
console.log("数据库打开成功");
resolve(db);
};
// 数据库打开失败的回调
request.onerror = function (event :any) {
console.log("数据库打开报错");
};
// 数据库有更新时候的回调
request.onupgradeneeded = function (event :any) {
// 数据库创建或升级的时候会触发
db = event.target.result; // 数据库对象
//初始化时增加表name
if (!db.objectStoreNames.contains("name" + StoreName)) {
const objectStore = db.createObjectStore("name" + StoreName, {
keyPath: "id", // 设置主键
autoIncrement: true, // 自增ID
});
objectStore.createIndex("userId", "userId", { unique: false });
}
//初始化时增加表App
if (!db.objectStoreNames.contains("APP" + StoreName)) {
const objectStore = db.createObjectStore("APP" + StoreName, {
keyPath: "id", // 设置主键
autoIncrement: true, // 自增ID
});
objectStore.createIndex("userId", "userId", { unique: false });//添加索引
objectStore.createIndex("uuid", "uuid", { unique: false });
objectStore.createIndex("mid", "mid", { unique: false });
}
};
});
}
- 数据库一旦打开,不能再进行任何的对表的增删改查操作
- 数据库中的数据想要进行批量查询,一定要给对应字段加上索引来增加搜索速度
- 该数据库打开时需要传入一个版本号,当数据库的结构发生变动时就会触发版本增加,需要用新的版本号来访问数据库
添加一条数据
/**
* 添加一条数据
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
* @param {object} data 数据
*/
export function addDB(db, storeName, data) {
var request = db
.transaction([storeName], "readwrite") // 事务对象
.objectStore(storeName) // 仓库对象
.put(data);
request.onsuccess = function () {
return true;
};
request.onerror = function () {
return false;
};
}
添加多条数据
/**
* 添加多条消息
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
* @param {array} data 数据数组
*/
export function addMultipleMessages(db, storeName, data) {
var requests = [];
data.forEach((message) => {
var request = db
.transaction([storeName], "readwrite") // 事务对象
.objectStore(storeName) // 仓库对象
.put({
userId: message.srcid,
mid: message.mid,
time: message.ts,
msg: message.msg,
type: message.type,
statu: 0,
location: 1,
});
requests.push(request);
});
Promise.all(requests)
.then((results) => {
return results.every((result) => result === true);
})
.catch((error) => {
console.error("Error adding messages:", error);
return false;
});
}
检查一条数据是否存在对应表中
// 假设已经打开了数据库并获取了数据库实例 db
export function checkIfExists(db :any, tableName :any, fieldName :any, fieldValue :any) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(tableName, "readonly");
const objectStore = transaction.objectStore(tableName);
const index = objectStore.index(fieldName);
const request = index.get(fieldValue);
request.onsuccess = function (event :any) {
const result = event.target.result;
if (result) {
// 存在对应的记录
resolve(true);
} else {
// 不存在对应的记录
resolve(false);
}
};
request.onerror = function (event :any) {
console.error("检查记录是否存在时出错:", event.target.error);
reject(event.target.error);
};
});
}
通过主键删除
/**
* 通过主键删除数据
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
* @param {object} id 主键值
*/
export function deleteDB(db :any, storeName :any, id :any) {
var request = db
.transaction([storeName], "readwrite")
.objectStore(storeName)
.delete(id);
request.onsuccess = function () {
console.log("数据删除成功");
};
request.onerror = function () {
console.log("数据删除失败");
};
}
通过索引和游标进行删除
/**
* 通过索引和游标删除指定的数据
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
* @param {string} indexName 索引名
* @param {object} indexValue 索引值
*/
export function cursorDelete(db :any, storeName :any, indexName :any, indexValue :any) {
var store = db.transaction(storeName, "readwrite").objectStore(storeName);
var request = store
.index(indexName) // 索引对象
.openCursor(IDBKeyRange.only(indexValue)); // 指针对象
request.onsuccess = function (e :any) {
var cursor = e.target.result;
var deleteRequest;
if (cursor) {
deleteRequest = cursor.delete(); // 请求删除当前项
deleteRequest.onerror = function () {
console.log("游标删除该记录失败");
};
deleteRequest.onsuccess = function () {
// console.log("游标删除该记录成功");
};
cursor.continue();
}
};
request.onerror = function (e) {};
}
- 游标cursor,是通过主键或者索引以及其他方式查询到的一系列数据的一个指针,内部含有iterate接口,可以进行遍历操作,从而进一步筛选出自己所需要的数据
查询并排序
/**
* 查询并排序数据
* @param {object} db 数据库实例
* @param {string} tableName 表名
* @param {string} field1 字段1名
* @param {string} field2 字段2名
* @param {any} valueToMatch 字段1的值
* @returns {Promise<Array>} 返回按字段2排序的数据数组
*/
export function queryAndSortData(db :any, tableName :any, field1 :any, field2 :any, valueToMatch :any) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(tableName, "readonly");
const objectStore = transaction.objectStore(tableName);
const index = objectStore.index("userId");
const range = IDBKeyRange.only(valueToMatch);
const results = [];
const cursorRequest = index.openCursor(range, "next");
cursorRequest.onsuccess = function (event :any) {
const cursor = event.target.result;
if (cursor) {
results.push(cursor.value);
cursor.continue();
} else {
// 对 results 数组按照 field2 字段排序
results.sort((a, b) => a[field2] - b[field2]);
resolve(results);
}
};
cursorRequest.onerror = function (event) {
reject(event.target.error);
};
});
}
通过主键读取
/**
* 通过主键读取数据 //读取的是一张表内key 为 string值的一条数据
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
* @param {string} key 主键值
*/
export function getDataByKey(db, storeName, key) {
return new Promise((resolve, reject) => {
var transaction = db.transaction([storeName]); // 事务
var objectStore = transaction.objectStore(storeName); // 仓库对象
var request = objectStore.get(key); // 通过主键获取数据
request.onerror = function (event) {
console.log("事务失败");
};
request.onsuccess = function (event) {
console.log("主键查询结果: ", request.result);
resolve(request.result);
};
});
}
通过游标读取
/**
* 通过游标读取数据 //读取的是一张表里的所有内容
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
*/
export function cursorGetData(db, storeName) {
return new Promise((resolve, reject) => {
let list = [];
var store = db.transaction(storeName, "readwrite").objectStore(storeName);
var request = store.openCursor();
request.onsuccess = function (e) {
var cursor = e.target.result;
if (cursor) {
// 必须要检查,防止指针溢出
list.push(cursor.value);
cursor.continue();
} else {
resolve(list); // 将数据传递给 resolve
}
};
request.onerror = function (event) {
reject(new Error("读取数据失败"));
};
});
}
通过索引读取
/**
* 通过索引读取数据 读取的是一张表内的索引 为 string值的一条数据
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
* @param {string} indexName 索引名称
* @param {string} indexValue 索引值
*/
export function getDataByIndex(
db: any,
storeName: any,
indexName: any,
indexValue: any
) {
return new Promise((resolve, reject) => {
var store = db.transaction(storeName, "readwrite").objectStore(storeName);
var request = store.index(indexName).get(indexValue);
request.onerror = function (event: any) {
reject(event.target.error);
};
request.onsuccess = function (e: any) {
var result = e.target.result;
resolve(result);
};
});
}
通过索引和游标混合查询
/**
* 通过索引和游标查询记录 游标和索引同时查询
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
* @param {string} indexName 索引名称
* @param {string} indexValue 索引值
*/
export function cursorGetDataByIndex(
db: any,
storeName: any,
indexName: any,
indexValue: any
) {
let list = [];
var store = db.transaction(storeName, "readwrite").objectStore(storeName); // 仓库对象
var request = store
.index(indexName) // 索引对象
.openCursor(IDBKeyRange.only(indexValue)); // 指针对象
request.onsuccess = function (e: any) {
var cursor = e.target.result;
if (cursor) {
// 必须要检查
list.push(cursor.value);
cursor.continue(); // 遍历了存储对象中的所有内容
} else {
console.log("游标索引查询结果:", list);
}
};
request.onerror = function (e: any) {};
}
条件分页查询
/**
* 条件分页查询记录
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
* @param {string} conditionField 条件字段名
* @param {any} conditionValue 条件值
* @param {number} page 页码
* @param {number} pageSize 查询条数
* @returns {Promise<Array>} 包含符合条件的分页数据的 Promise
*/
export function conditionalCursorPagination(
db: any,
storeName: any,
conditionField: any,
conditionValue: any,
page: any,
pageSize: any
) {
return new Promise((resolve, reject) => {
let list = [];
let counter = 0; // 计数器
var store = db.transaction(storeName, "readonly").objectStore(storeName); // 使用"readonly"事务
var index = store.index(conditionField); // 获取索引对象
var range = IDBKeyRange.only(conditionValue); // 创建范围
var request = index.openCursor(range,"prev"); // 打开游标
request.onsuccess = function (e: any) {
var cursor = e.target.result;
if (cursor) {
if (counter >= (page - 1) * pageSize && counter < page * pageSize) {
// 仅在符合分页条件时添加到结果列表
list.push(cursor.value);
}
counter++;
if (counter < page * pageSize) {
cursor.continue(); // 继续下一个游标
} else {
resolve(list); // 解决 Promise 并返回结果
}
} else {
resolve(list); // 解决 Promise 并返回结果
}
};
request.onerror = function (e: any) {
reject(e.target.error); // 处理错误并拒绝 Promise
};
});
}
更新一个字段
/**
* 更新数据
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
* @param {string} fieldName 字段名称
* @param {any} fieldValue 字段值
* @param {object} newData 新数据
*/
export function updateDataByField(
db,
storeName,
fieldName,
fieldValue,
newData
) {
var transaction = db.transaction([storeName], "readwrite");
var store = transaction.objectStore(storeName);
// 使用索引查找数据
var index = store.index(fieldName); // 假设字段 "fieldName" 已经创建了名为 "fieldNameIndex" 的索引
var request = index.get(fieldValue);
request.onsuccess = function (event) {
var result = event.target.result;
newData.id = result.id;
if (result) {
// 找到了数据,使用主键更新
var updatedData = { ...result, ...newData };
var updateRequest = store.put(updatedData);
updateRequest.onsuccess = function () {
// console.log("数据更新成功");
};
updateRequest.onerror = function () {
console.log("数据更新失败");
};
} else {
console.log("未找到匹配的数据");
}
};
}
条件批量更新某一字段
/**
* 更新 unReadMsgNum 字段的数据
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
* @param {string} fieldName 字段名称
* @param {any} fieldValue 字段值
* @param {number} newUnReadMsgNum 新的未读消息数
* @param {boolean} accumulate 是否累加未读消息数
*/
export function updateUnReadMsgNum(
db,
storeName,
fieldName,
fieldValue,
newUnReadMsgNum,
accumulate = false
) {
var transaction = db.transaction([storeName], "readwrite");
var store = transaction.objectStore(storeName);
// 使用索引查找数据
var index = store.index(fieldName); // 假设字段 "fieldName" 已经创建了名为 "fieldNameIndex" 的索引
var request = index.get(fieldValue);
request.onsuccess = function (event) {
var result = event.target.result;
if (result) {
// 找到了数据,更新 unReadMsgNum 字段
var updatedData = { ...result };
if (accumulate) {
updatedData.unReadMsgNum += newUnReadMsgNum; // 累加未读消息数
} else {
updatedData.unReadMsgNum = newUnReadMsgNum; // 直接覆盖未读消息数
}
var updateRequest = store.put(updatedData);
updateRequest.onsuccess = function () {
// console.log("unReadMsgNum 数据更新成功");
};
updateRequest.onerror = function () {
console.log("unReadMsgNum 数据更新失败");
};
} else {
console.log("未找到匹配的数据");
}
};
}
- 书写这段代码是我正在书写im通讯,业务时更新所有未读消息的状态
复杂更新
/**
* 在数据库中查找满足字段1等于字段值1且字段2等于字段值2的记录,
* 并将这些记录中字段2的值修改为字段值3
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
* @param {string} fieldName1 第一个字段名称
* @param {any} value1 第一个字段的值
* @param {string} fieldName2 第二个字段名称
* @param {any} value2 第二个字段的值
* @param {any} value3 新的字段值2
* @returns {Promise} Promise 对象,表示操作是否成功完成
*/
export function updateMsgStatus(
db,
storeName,
fieldName1,
value1,
fieldName2,
value2,
value3
) {
return new Promise((resolve, reject) => {
var transaction = db.transaction([storeName], "readwrite");
var store = transaction.objectStore(storeName);
// 使用索引查找满足字段1等于字段值1的记录
var index = store.index(fieldName1);
var getRequest = index.openCursor(IDBKeyRange.only(value1), "next");
var recordsToUpdate = []; // 存放要更新的记录
getRequest.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
var record = cursor.value;
if (record[fieldName2] === value2) {
recordsToUpdate.push(record); // 将满足条件的记录存入数组
}
cursor.continue(); // 继续遍历下一条记录
} else {
// 遍历完所有记录后,依次更新记录并处理结果
recordsToUpdate.forEach((record) => {
// 修改字段2的值为新值
record[fieldName2] = value3;
// 更新记录并处理结果
var updateRequest = store.put(record);
updateRequest.onsuccess = function () {
// 更新成功
};
updateRequest.onerror = function () {
// 更新失败
};
});
resolve(1); // 所有满足条件的记录已更新
}
};
getRequest.onerror = function () {
reject(new Error("获取记录时发生错误"));
};
});
}
关闭数据库
/**
* 关闭数据库
* @param {object} db 数据库实例
*/
export function closeDB(db :any) {
db.close();
console.log("数据库已关闭");
}
- 用完数据库后记得关闭,从而节约资源
调试页面
在该页面可以清楚的看到所有存储到indexedDB数据库中的内容
4. WebSql
已经废弃,不再使用
5. Cookie
cookie
❝
HTTP cookie通常也叫做cookie,最初用于在客户端存储会话信息。「cookie是与特定域名绑定的,设置cookie后,它会与请求一起发送到创建它的域。」 这个限制能保证cookie中存储的信息只对被认可的接收者开放,不被其它域访问。❞
cookie的构成
- 「名称:」 唯一标识cookie的名称。cookie不区分大小写,cookie名必须经过URL编码。
- 「值:」 存储在cookie里的字符串值,这个值必须经过URL编码。
- 「域:」 cookie的有效域,发送到这个域的所有请求都会包含对应的cookie。
- 「路径:」 请求URL中包含这个路径才会把cookie发送到服务器。
- 「过期时间:」 表示何时删除cookie的时间戳。(即什么时间之后就不会发送到服务器了)
- 「安全标识:」 设置之后,只在使用SSL安全连接的情况下才会把cookie发送到服务器。
限制
「因为cookie存储在客户端机器上,所以为保证它不会被恶意利用,浏览器会施加限制。」
- 不超过300个cookie
- 每个cookie不超过4096字节(4kb)
- 每个域不超过20个cookie
- 每个域不超过81920字节
JavaScript中的cookie
在JavaScript中处理cookie比较麻烦,因为接口过于简单,只有**「BOM」** 的document.cookie属性。
获取cookie
document.cookie返回包含页面中所有有效cookie的字符串,以分号分隔;
ini
复制代码name1=value1;name2=valve2;name3=value3
「所有名和值都是URL编码,因此必须使用decodeURIComponent()解码」
设置cookie
javascript复制代码name=value; expires=expiration_time; path=domain_path; domain=domain_namel secure
//在所有这些参数中,只有cookie的名称和值是必须的
document.cookie = `${encodeURIComponent('name')}=${encodeURIComponent('nanjiu')};domain=bettersong.github.io;`
删除cookie
「没有直接删除已有cookie的方法,但可以通过设置其过期时间来达到删除效果;」
javascript
复制代码document.cookie = 'uid=dkfywqkrhkwehf23;expires=' + new Date(0) + ';path=/;secure;
cookie是如何工作的?
「request:」
当浏览器发起一个请求时,浏览器会自动检查是否有相应的cookie,如果有则将cookie添加到Request Headers的Cookie字段中
「response:」
当服务器需要cookie时,在http请求的Response Headers字段中添加Set-Cookie字段,浏览器接收到之后会自动解析识别,将cookie种下。
6. localforage
通过上述描述后,大家也能发现,indexedDB数据库虽然强大,但是数据操作起来异常负责,因此在indexedDB数据库的基础上封装出来了LocalForage
兼容性:它有一个优雅降级策略,若浏览器不支持 IndexedDB 或 WebSQL,则使用 localStorage。在所有主流浏览器中都可用:Chrome,Firefox,IE 和 Safari(包括 Safari Mobile)
导入
import localforage from 'localforage'
创建
const myIndexedDB = localforage.createInstance({
name: 'IndexedDB',
})
存值
IndexedDB.setItem(key, value)
取值
由于indexedDB的存取都是异步的,建议使用 promise.then() 或 async/await 去读值
myIndexedDB.getItem('somekey').then(function (value) {
}).catch(function (err) {
});
try {
const value = await myIndexedDB.getItem('somekey');
} catch (err) {
console.log(err);
}
删除
IndexedDB.removeItem('somekey')
重置
IndexedDB.clear()