2023年了你还只会localStorage?一文带你了解前端存储方案。

方案简介

名称同步/异步大小限制生命周期支持度同源策略
sessionStorage同步4kb临时性良好遵循
localStorage同步5m左右永久存储良好遵循
Indexed异步promise2GB 左右永久存储一般遵循
WebSql异步promise10MB左右永久存储遵循
Cookie同步4kb临时性良好遵循
localforage异步promise2GB 左右永久存储一般遵循

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 HeadersCookie字段中

「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()

参考文章:这一次带你彻底了解前端本地存储 - 掘金localForage介绍-CSDN博客

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值