本地存储对于一个前端来说是一项必不可少的技能,虽然你一直在使用,但是你不一定全部了解,那么现在又开始老夫的装B时刻-show time
温馨提示:文章结构如下,阅读完可能需要花费5分钟
一、 cookie : 为了浏览器兼容性 H5之前很长一段时间用, 但是现在不怎么用啦
二、 storage: (包含 localstorage/ sessionstorage)本地存储
三、WebSQL: 网页版的数据库 - 很少使用,但是还是讲讲
四、 indexDB: 本地缓存数据库 - 可以替代WebSQL
进入正文
一、 cookie
-
cookie的优点:
A.基本所有浏览器都适用
B.同源http 中携带 -只要有请求涉及cookie,cookie就要在服务器和浏览器之间来回传送 cookie的缺点
A.cookie空间大小:4K,不适合存业务数据
B .cookie安全性问题:由于在HTTP请求中的cookie是明文传递的(HTTPS不是),带来的安全性问题还是很大的
C.网络负担:我们知道cookie会被附加在每个HTTP请求中,请求和返回都会有,浪费流量
-
cookie 常用的API
// 设置cookie
function setCookie(c_name, value, expireDay, path, domain, secure = true) {
let cookieName = encodeURIComponent(c_name) + '=' + encodeURIComponent(value);
if (expireDay) {
const exdate=new Date()
exdate.setDate(exdate.getDate() + expiredays);
cookieName += ';expires=' + exdate.toUTCString()
}
if (path) {
cookieName += ';path' + path
}
if (domain) {
cookieName += ';domain=' + domain
}
if (secure) {
cookieName += ';secure=' + secure
}
document.cookie = cookieName;
}
/**- 获取cookie
/
function getCookie(c_name){
if(document.cookie.length > 0){
const cookieName = encodeURIComponent(c_name) + '=';
const startIndex = document.cookie.indexof(cookiename);
let cookieValue = ''
if(startIndex !== -1){
let endIndex = document.cookie.indexof(';', startIndex);
if(endIndex === -1) {
endIndex = document.cookie.length;
}
//获取对应cookieName的value值
cookieValue = document.cookie.substring(startIndex + cookieName.length, endIndex)
}
}
}
/ - 移除cookie
*/
function deleteCookie(c_name, value) {
if(document.cookie.length > 0){
const cookieName = encodeURIComponent(c_name) + '=' + encodeURIComponent(value);
const exp = new Date(0);
document.cookie = cookieName + ';expires=' + exp.toUTCString();
}
}
- 获取cookie
参数说明
name: 名称
value: 值
path:路径
expires:过期时间,如果不设置过期时间,关闭浏览器消失,如果设置过期时间存到硬盘中,默认存在内存中
secure: 安全
domain:web 共享cookie适用场景
cookie 空间小,但是可以利用http来回传输的优点可以保存用户id 或者token验证信息,但是要注意加密
注意:一般情况我们很少去封装cookie, 因为有插件js-cookies
二. storage - 本地存储我们最常用
1.分类:
A-localStorage: 本地永久性存储数据,除非手动将其删除或清空
B-sessionStorage:存储的数据只在会话期间有效(会话存储、临时存存储),关闭浏览器则自动删除
2.存储方式 : key- value 键值对方式
3.存储大小 : 每个域名5M
4.存储内容:数组,图片,json,样式等(只要是能序列化成字符串的内容都可以存储)都需要序列化为字符串类型
5.浏览器兼容情况: IE8.0+ Chrome 4.0+ FireFox 3.0+ 等
6.常用的API:
- length:唯一的属性,只读,用来获取storage内的键值对数量;
- key:根据index获取storage的键名
- getItem:根据key获取storage内的对应value
- setItem:为storage内添加键值对
- removeItem:根据键名,删除键值对
- clear:清空storage对象
7.事件StorageEvent -监听storage 变化
- 使用场景
保存少量业务数据,比如用户信息,但是需要加密
- 怎么设计一个storage 工具类
A.、因为空间有限,应可配置数据失效时间
B、 需要处理内存溢出
C、最好有加密
下面是老夫参考别人设计的一个简单类(暂时没有考虑加密,不想看的可以直接略过)
/**
* 本地保存localstorage
*/
import ZBConsole from "../common/ZBConsole.js";
const LocalStorageHelper = {
/**
* 保存数据
* @param key 保存的key
* @param value 保存的值
* @param expires 过期时间
*/
setValue(key, value, expires){
const _d = new Date();
// 存入日期
const inTime =_d.getTime();
if(!expires){
_d.setTime(inTime + this.defaultExpeierTime);
expires = _d.getTime();
}
if(!this.__isSupportValueType(value)){
return false;
}
// 保存所有的key 数组
this.__setKeyCache(key, expires);
// 需要保存的对象
const entity = this.__createStorageObj(value,inTime,expires);
const entityStr = JSON.stringify(entity);
// 保存对象
try{
this.storeProxy.setItem(key, entityStr);
return true;
}catch (e) {
// localstorage内存溢出
if(e.name === 'QuotaExceededError'){
// 溢出过期时间最长的数据
if(!this.__removeLastCache()){
this.__asset('溢出移除数据失败');
}
// 重新保存
this.setValue(key,value,expires);
}
}
return false;
},
/**
* 获取数据
*/
getValue(key){
const nowTime = new Date().getTime();
let resultObject = null;
try {
const resultObjectStr = this.storeProxy.getItem(key);
if(!resultObjectStr || resultObjectStr.length == 0){
return null;
}
resultObject = JSON.parse(resultObjectStr);
//数据过期
if (resultObject.expires < nowTime){
return null;
}
// 数据正常
const valueStr = resultObject.value;
if(valueStr === this.dataTypes.type_Array
|| valueStr === this.dataTypes.type_Object){
const dataList = JSON.stringify(valueStr);
return dataList;
}else {
return valueStr;
}
}catch (e) {
console.log('获取数据失败--',e);
}
},
/**
* 溢出过期时间最近的的数据
*/
__removeLastCache(){
// 移除次数
const num = this.removeNum || 5;
//取出键值对
const cacheStr = this.storeProxy.getItem(this.keyCache);
// 说明没有数据需要移除
if (!cacheStr || cacheStr.length === 0){
return false;
}
const cacheKeyMap = JSON.parse(cacheStr);
if(!_.isArray(cacheKeyMap)){
return false;
}
// 时间排序
cacheKeyMap.sort(function (a,b) {
return a.expires - b.expires;
});
// 删除数据
const delKeyMap = cacheKeyMap.splice(0, num);
for (let i = 0; i < delKeyMap.length; i++) {
const item = delKeyMap[I];
this.storeProxy.removeItem(item.key);
}
// 保存key
this.storeProxy.setItem(this.keyCache, JSON.stringify(cacheKeyMap));
return true;
},
/**
* 移除过期的数据
*/
removeExpiresCache(){
//1. 取出当前的所有key
const cacheKeyStr = this.storeProxy.getItem(this.keyCache);
if(!cacheKeyStr || cacheKeyStr.length === 0){
return false;
}
const cacheKeyMap = JSON.parse(cacheKeyStr);
if(!_.isArray(cacheKeyMap)){
return false;
}
// 2.获取当前时间
const nowTime = new Date().getTime();
// 3.比较过期时间
const newKeyMap = [];
for (let i = 0; i < cacheKeyMap.length; i++) {
const item = cacheKeyMap[I];
if(item.expires < nowTime){
// 已经过期
this.storeProxy.removeItem(item.key);
}else {
// 没有过期
newKeyMap.push(item);
}
}
const jsonStr = JSON.stringify(newKeyMap);
this.storeProxy.setItem(this.keyCache, jsonStr);
return true;
},
/**
* 保存key到内存中
*/
__setKeyCache(key, expires){
if (!key || !expires || expires < new Date().getTime()){
return false;
}
const saveStr = this.storeProxy.getItem(this.keyCache);
const obj = {};
obj.key = key;
obj.expires = expires;
// 1.之前保存的数据为null
let saveList = [];
if(!saveStr || saveStr.length == 0){
saveList.push(obj);
return true;
}
// 2. 之前的保存的数据有值但是不是数组
saveList = JSON.parse(saveStr);
if(!_.isArray(saveList)){
saveList = [];
saveList.push(obj);
return true;
}
// 3. 判断key值是否是之前保存的
let findKey = false;
for (let i = 0; i < saveList.length; i++) {
let item = saveList[I];
if(item.key === key){
item = obj;
findKey = true;
break;
}
}
if(!findKey){
saveList.push(obj);
}
// 4. 保存所有的key
const jsonStr = JSON.stringify(saveList);
this.storeProxy.setItem(this.keyCache, jsonStr);
},
/**
* 创建保存的object
*/
__createStorageObj(vaule, inTime, expires){
if(!vaule){
this.__asset('保存值不能为null');
return;
}
let saveValue = vaule;
if(vaule instanceof Object){
saveValue = JSON.stringify(vaule);
}
const obj = {
value: saveValue,
inTime: inTime,
expires: expires,
type: Object.prototype.toString.call(vaule),
}
return obj;
},
/**
* 是否支持这个类型是值
*/
__isSupportValueType(value){
if(!value){
this.__asset('保存的value不能为null');
return false;
}
const objectStr = Object.prototype.toString.call(value);
if(objectStr === "[object Symbol]"){
this.__asset('保存的value不支持Symbol类型');
return false;
}
return true;
},
/**
* 初始化
*/
__initialize(){
this.__propertys();
},
/**
* 设置默认属性
*/
__propertys(){
//代理对象,默认为localstorage
this.storeProxy = window.localStorage;
if(!this.storeProxy){
this.__asset('浏览器不支持localstorage');
return false;
}
//60 * 60 * 24 * 30 * 1000 ms ==30天 过期时间
this.defaultExpeierTime = 2592000000;
//本地缓存用以存放所有localstorage键值与过期日期的映射
this.keyCache = 'SYSTEM_KEY_TIMEOUT_MAP';
//当缓存容量已满,每次删除的缓存数
this.removeNum = 5;
// 数据类型
this.dataTypes = {
type_Array: "[object Array]",
type_Object: "[object Object]",
type_Boolean: "[object Boolean]",
type_String: "[object String]",
type_Number: "[object Number]",
type_Symbol : "[object Symbol]",
}
},
/**
* 断言
*/
__asset(message){
ZBConsole.logTrace(message);
throw message;
}
};
;(function(window){
// 暴露类
function ZBStorage() {
LocalStorageHelper.__initialize();
// 暴露方法
ZBStorage.prototype.setValue = function(key, value, expires){
LocalStorageHelper.setValue(key, value, expires)
}
ZBStorage.prototype.getValue = function (key) {
return LocalStorageHelper.getValue(key)
}
}
window.ZBStorage = new ZBStorage();
})(window);
export default LocalStorageHelper;
结果:
三. WebSQL - 可以在最新版的 Safari, Chrome 和 Opera 浏览器中使用,但是很少用
相对有过数据库经验的人很简单,基本就是执行SQL语句
支持情况
Web SQL 数据库可以在最新版的 Safari, Chrome 和 Opera 浏览器中工作核心API
openDatabase:新建的数据库创建一个数据库对象或者打开已有的数据库
transaction: 执行SQL语句的事务
executeSql: 执行SQL语句
3.存储大小: 自定义设置大小
使用场景:对数据库设计比较了解的人,存储大量数据
怎么设计存储工具类
/**
* web sql Web SQL 数据库可以在最新版的 Safari, Chrome 和 Opera 浏览器中工作
*/
export default {
/**
* 打开数据库
*/
openSQLite(){
if(!this.sqlDB){
// 数据库名称、版本号、描述文本、数据库大小、创建回调
this.sqlDB = openDatabase('zbsqlite','1.0.0','数据库', 2*1024*1024, function () {
});
}
},
/**
* 执行sql 语句
*/
executeSql(sql){
this.openSQLite();
if(!sql || sql.length == 0){
return;
}
this.sqlDB.transaction(function (tx) {
tx.executeSql(sql);
});
},
/**
* 创建表
*/
createTable(tableName){
const sql = `CREATE TABLE IF NOT EXISTS ${tableName} (id unique, name, age)`;
this.executeSql(sql);
},
/**
* 插入数据
*/
insetTable(){
const sql_data01 = 'INSERT INTO zbtestTable (id, name, age) VALUES (1, "甄姬", 15)';
const sql_data02 ='INSERT INTO zbtestTable (id, name, age) VALUES (2, "安其拉", 20)';
this.executeSql(sql_data01);
this.executeSql(sql_data02);
},
/**
* 查询数据
*/
queryTable(){
const sql = "SELECT * FROM zbtestTable";
this.executeSql(sql);
}
}
结果:
四. indexDB - 索引数据库(可以用保存数据/离线缓存)
- 特点:
A. 异步API-请求-响应的模式保存与获取数据,不是我们熟悉的key-value,但是存储方式键值对储存;
B.索引数据库没有表的概念,但是有objectStore, 就是类似于数据库的表
C.一个数据库中可以包含多个objectStore
D. 不属于关系型数据库(不支持 SQL 查询语句)
E. 储存空间大 IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限
F.同源限制 IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库
2.对象的基本概念
数据库:IDBDatabase 对象
对象仓库:IDBObjectStore 对象
索引: IDBIndex 对象
事务: IDBTransaction 对象
操作请求:IDBRequest 对象
指针: IDBCursor 对象
主键集合:IDBKeyRange 对象
- 操作流程
3.1 创建或者打开数据库
/**
* indexDB对象
*/
indexDBOptions:{
name: "test-01",
version: 1,
db:null,
},
/**
* 打开数据库
*/
openIndexDB(storeName, callBack){
if(!this.indexDBOptions.db){
// open(name: string, version?: number) name 数据库的名字 version: 版本
const dbRequest = window.indexedDB.open(this.indexDBOptions.name, this.indexDBOptions.version);
const that = this;
// success事件表示成功打开数据库。
dbRequest.onsuccess = function (event) {
const db = event.target.result;
that.indexDBOptions.db = db;
if(callBack){
callBack();
}
};
// error事件表示打开数据库失败
dbRequest.onerror = function (event) {
console.log("数据库打开失败--------" || e.currentTarget.error.message);
};
// 如果指定的版本号,大于数据库的实际版本号,就会发生数据库升级事件upgradeneeded
dbRequest.onupgradeneeded=function(e){
const db = e.target.result;
console.log('DB version changed to '+ db.version);
if(!db.objectStoreNames.contains(storeName)){
// 创建store 新建对象仓库(即新建表) 主键是id
const store = db.createObjectStore(storeName, {keyPath:"id"});
// 创建索引 IDBObject.createIndex()的三个参数分别为索引名称、索引所在的属性、配置对象(说明该属性是否包含重复的值)
store.createIndex('nameIndex', 'name', {unique: true});
store.createIndex('ageIndex', 'age', {unique: true});
}
};
}
},
3.2 写入数据
/**
* 获取数据表 objectStore
*/
getStore(storeName, callBack){
if(this.indexDBOptions.db){
// "readonly" | "readwrite" | "versionchange" 三种方式
const transation = this.indexDBOptions.db.transaction(storeName, "readwrite");
const store = transation.objectStore(storeName);
if(callBack){
callBack(store);
}
}else {
const that = this;
this.openIndexDB(storeName, function () {
that.getStore(storeName, callBack);
});
}
},
/**
* 保存数据
*/
addDataToDB(storeName, dataList){
const that = this;
// 1. 创建表
this.openIndexDB(storeName ,function () {
// 2. 获取表
that.getStore(storeName, function (store) {
// 3.保存数据
for (let i = 0; i < dataList.length; i++) {
const item = dataList[I];
store.add(item);
}
});
});
},
const persons = [
{
id: 1000,
name: "亚瑟",
age: 15,
},
{
id: 1001,
name: "安其拉",
age: 20,
},
{
id: 1002,
name: "典韦",
age: 18,
}
];
// IndexDBHepler 工具类
IndexDBHepler.openIndexDB('person-test');
IndexDBHepler.addDataToDB('person-test',persons);
结果:
3.3 获取数据 - 三种方式
通过key获取数据
通过索引获取数据
-
通过游标获取
/** * 获取数据 通过key */ getDataByKeyFromDB(storeName, key, callBack){ // 1. 获取表 this.getStore(storeName, function (store) { const request= store.get(key) request.onsuccess = function (e) { const person=e.target.result; if(callBack){ callBack(person); } console.log("通过key获取数据----",person); } }); }, /** * 通过索引获取数据 */ getDataByIndexFromDB(storeName, indexName, value, callBack){ this.getStore(storeName, function (store) { const index = store.index(indexName); const request = index.get(value); request.onsuccess = function (e) { const person=e.target.result; if(callBack){ callBack(person); } console.log("通过Index获取数据----",person); } }); }, /** * 通过游标获取 */ getDataByCursorFromDB(storeName, callBack){ this.getStore(storeName, function (store) { const request = store.openCursor(); request.onsuccess = function (e) { const cursor=e.target.result; if(cursor){ const person=cursor.value; console.log('通过游标获取---',person); cursor.continue(); } } }); },
3.4关闭和删除数据库
/**
* 关闭数据库
*/
closeIndexDB(){
if(this.indexDBOptions.db){
this.indexDBOptions.db.close();
}
},
/**
* 删除数据库
*/
deleteIndexDB(name){
const indexName = this.indexDBOptions.name || name;
this.closeIndexDB();
window.indexedDB.deleteDatabase(indexName);
}
- 使用场景
存储大量数据,比如页面离线缓存等
最后,以上总结都是老夫为了方便理解自己总结的内容,其中参考以下资料,创作不易,尊重原著
参考文献链接
http://www.ruanyifeng.com/blog/2018/07/indexeddb.html
H5系列
Web前端基础篇-HTML-01-BOM浏览器对象模型
Web前端基础篇-HTML-02-HTML的生命周期
Web前端基础篇-HTML-03-事件处理系统
Web前端基础篇-HTML-04-HTML 渲染流程
Web前端基础篇-HTML5-05-最全本地存储总结
Web前端基础篇-HTML5-06-离线缓存AppCache
Web前端基础篇-HTML5-07-浏览器缓存机制