方案简介
名称 | 同步/异步 | 大小限制 | 生命周期 | 支持度 | 同源策略 |
---|---|---|---|---|---|
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()