前言
现在运用的在浏览器本地存储信息的方法主要有三种:cookie、sessionstorage/localstorage、IndexedDB。这三种方式从各个方面都有一些不同,接下来就介绍这三种方法的使用:
cookie
-
创建(修改):
document.cookie = "name=value; expires=expirationDate; path=path; domain=domain; secure";- 参数分别为:键值对(主要存储内容),失效时间(不写就是关闭就失效),存储路径(默认当前页面),作用域(默认当前域名及其子域),安全选项(
- Secure:表示 Cookie 只能通过 HTTPS 协议传输。
- HttpOnly:表示 Cookie 不能通过 JavaScript 访问,只能通过 HTTP 请求访问。)
- 修改和创建语法相同,键存在就是修改,键不存在就是创建。
- 参数分别为:键值对(主要存储内容),失效时间(不写就是关闭就失效),存储路径(默认当前页面),作用域(默认当前域名及其子域),安全选项(
-
删除:语法和创建、修改的相同,只要把失效时间改成现在就行。
-
获取:可以直接通过document.cookie以字符串形式获取所有cookie,也可以通过split()方法分割字符串获取指定cookie。
function getCookie(cname)
{
var name = cname + "=";
var ca = document.cookie.split(';');
for(var i=0; i<ca.length; i++)
{
var c = ca[i].trim();
if (c.indexOf(name)==0) return c.substring(name.length,c.length);
}
return "";
}
sessionStorage/localStorage
这两种方式在H5中引入,可以将数据存储在浏览器本地,但是存储期限不同,sessionStorage的存储期限为当前会话,关闭浏览器窗口则数据清除,localStorage的存储期限为永久,除非手动清除。两种方式的访存方式相同。
- setItem(key, value):存储数据。
- getItem(key):获取数据。
- removeItem(key):删除特定数据。
- clear():删除所有数据。
- storage事件:监听存储变化。
window.addEventListener('storage', (event) => {
console.log('存储发生变化:', event.key, event.newValue);
});
IndexedDB
IndexedDB是HTML5中提供的本地数据库,可以存储结构化数据,支持事务,查询,索引等功能。
IndexedDB通常采用事务机制进行操作,事务可以保证一组操作要么全部成功,要么全部失败,此外还具有异步操作的特点,不会影响浏览器其他的操作。
创建/加载数据库
下面是一段创建/加载数据库的示例代码: 注:本部分的示例代码均来自这个文章:原贴链接:https://juejin.cn/post/7026900352968425486 ,较原贴略有改动
function openDB(dbName, version = 1) {
// 这里他通过函数进行封装创建数据库的操作,返回一个Promise对象(体现异步的特性)
return new Promise((resolve, reject) => {
let db; // 用于存储数据库对象
// 打开数据库,若没有则会创建
const request = window.indexedDB.open(dbName, version);
// 数据库打开成功回调
request.onsuccess = function (event) {
db = event.target.result; // 获取数据库对象
console.log("数据库打开成功");
resolve(db);
};
// 数据库打开失败的回调
request.onerror = function (event) {
console.log("数据库打开报错");
};
// 数据库有更新时候的回调
request.onupgradeneeded = function (event) {
// 数据库创建或升级的时候会触发
console.log("onupgradeneeded");
db = event.target.result; // 将更新的数据库对象同步给db变量
var objectStore;
// 创建存储库
objectStore = db.createObjectStore("signalChat", {
keyPath: "sequenceId", // 这是主键
// autoIncrement: true // 实现自增
});
// 创建索引,在后面查询数据的时候可以根据索引查,前一个值为索引名称,后一个为索引关联值
objectStore.createIndex("link", "link", { unique: false });
objectStore.createIndex("sequenceId", "sequenceId", { unique: false });
objectStore.createIndex("messageType", "messageType", {
unique: false,
});
};
});
}
存储/更新数据
存储数据通常采用事务机制进行操作,事务可以保证一组操作要么全部成功,要么全部失败,保证存储的准确性。
下面同样是一段示例代码:
function addData(db, storeName, data) { // 数据库变量,存储表名,数据对象
// 这里他通过函数进行封装存储数据的操作
var request = db
.transaction([storeName], "readwrite") // 创建事务对象 指定表格名称和操作模式("只读"或"读写")
.objectStore(storeName) // 获取存储仓库(数据表)对象
.add(data); // 添加操作,注意如果数据表创建的时候没选择主键自增,则插入的对象必须包含主键元素
request.onsuccess = function (event) {
console.log("数据写入成功");
};
request.onerror = function (event) {
console.log("数据写入失败");
};
}
此外还可以通过put()方法更新数据,如果这个键原来没有则插入新数据,因此也可以代替add()方法。
function updateDB(db, storeName, data) {
var request = db
.transaction([storeName], "readwrite") // 事务对象
.objectStore(storeName) // 仓库对象
.put(data);
request.onsuccess = function () {
console.log("数据更新成功");
};
request.onerror = function () {
console.log("数据更新失败");
};
}
删除数据
下面同样是一段示例代码:
// 通过主键删除数据
function deleteDB(db, storeName, id) {
var request = db
.transaction([storeName], "readwrite")
.objectStore(storeName)
.delete(id);
request.onsuccess = function () {
console.log("数据删除成功");
};
request.onerror = function () {
console.log("数据删除失败");
};
}
此外还有通过索引游标遍历删除的方法:
function cursorDelete(db, storeName, indexName, indexValue) {
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.onerror = function () {
console.log("游标删除该记录失败");
};
deleteRequest.onsuccess = function () {
console.log("游标删除该记录成功");
};
cursor.continue();
}
};
request.onerror = function (e) {};
}
查询数据
查询数据通常是数据库中最常用同时也是最重要的操作,在IndexedDB中查询数据也采用事务机制进行操作,同时有多种查询的方式:
通过主键查找
下面是一段示例代码
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);
};
});
}
通过游标查找
游标可以简单理解为C++中的迭代器,可以用于遍历所有的元素
下面是一段示例代码:
function cursorGetData(db, storeName) {
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 {
console.log("游标读取的数据:", list);
}
};
}
通过索引查找
由于在IndexedDB中数据是以键值对而不是数据表存储,所以构建索引可以使查找更灵活,不在局限于主键,能够适应更多的场景。
下面是一段示例代码:
function getDataByIndex(db, storeName, indexName, indexValue) {
var store = db.transaction(storeName, "readwrite").objectStore(storeName);
var request = store.index(indexName).get(indexValue); // 通过索引获取数据
request.onerror = function () {
console.log("事务失败");
};
request.onsuccess = function (e) {
var result = e.target.result;
console.log("索引查询结果:", result);
};
}
这种方法在返回第一个满足索引匹配的对象时就会返回,但索引通常不是唯一的,会有多个需要返回的对象,这时候就需要下面这种方法
索引游标结合查找
这种方法通过索引游标结合查找,遍历所有数据库中的值,能够返回所有满足索引的值
function cursorGetDataByIndex(db, storeName, indexName, indexValue) {
let list = [];
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;
if (cursor) {
// 必须要检查
list.push(cursor.value);
cursor.continue(); // 遍历了存储对象中的所有内容
} else {
console.log("游标索引查询结果:", list);
}
};
request.onerror = function (e) {};
}
关闭与删除数据库
在使用数据库后,可以使用db.close()方法关闭数据库,节省资源,此外还可以通过以下的方法删除数据库.
function deleteDBAll(dbName) {
console.log(dbName);
let deleteRequest = window.indexedDB.deleteDatabase(dbName);
deleteRequest.onerror = function (event) {
console.log("删除失败");
};
deleteRequest.onsuccess = function (event) {
console.log("删除成功");
};
}
总结
| 维度 | Cookie | localStorage / sessionStorage | IndexedDB |
|---|---|---|---|
| 数据体积 | ≤ 4 KB(单域名总量 ≤ 20 左右) | 5~10 MB(浏览器不同略有差异) | 理论 50% 剩余硬盘空间,实测可达数百 MB 以上 |
| 生命周期 | 手动设置 expires/max-age;默认随会话结束 | localStorage:永久;sessionStorage:标签页关闭即失效 | 永久,除非用户手动清理或代码删除数据库 |
| 是否随 HTTP 请求 | 每次都自动带在请求头(增加流量) | 完全不参与 | 完全不参与 |
| 数据类型 | 字符串 only | 字符串 only | 结构化对象(ArrayBuffer、Blob、ImageData…) |
| 查询能力 | 无,只能整字符串取回自己 split | 无,只能 key→value 直接索引 | 支持事务、游标、索引、范围查询、主键复合查询 |
| 同步/异步 | 同步 | 同步 | 异步(Promise / callback) |
| 同源策略 | 子域可共享(domain 显式设置) | 严格同源 | 严格同源 |
| 浏览器兼容性 | 最老(IE6) | IE8+ | IE10+,现代浏览器全支持 |
| 常见痛点 | 容量小、每次请求头浪费流量、需要自行做 CSRF 防护 | 只能存字符串、大 JSON 需手动序列化、同步 API 阻塞渲染 | API 冗长、学习曲线陡、需要写事务回调 |
