本地存儲對于一個前端來說是一項必不可少的技能,雖然你一直在使用,但是你不一定全部了解,那么現在又開始老夫的裝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-瀏覽器緩存機制