Web前端基礎篇-HTML5-05-最全本地存儲總結

本地存儲對于一個前端來說是一項必不可少的技能,雖然你一直在使用,但是你不一定全部了解,那么現在又開始老夫的裝B時刻-show time


tmpdir__17_9_7_15_04_07.jpg

溫馨提示:文章結構如下,閱讀完可能需要花費5分鐘

一、 cookie : 為了瀏覽器兼容性 H5之前很長一段時間用, 但是現在不怎么用啦
二、 storage: (包含 localstorage/ sessionstorage)本地存儲
三、WebSQL: 網頁版的數據庫 - 很少使用,但是還是講講
四、 indexDB: 本地緩存數據庫 - 可以替代WebSQL

進入正文

一、 cookie
  1. cookie的優點:

    A.基本所有瀏覽器都適用
    B.同源http 中攜帶 -只要有請求涉及cookie,cookie就要在服務器和瀏覽器之間來回傳送

  2. cookie的缺點

A.cookie空間大小:4K,不適合存業務數據
B .cookie安全性問題:由于在HTTP請求中的cookie是明文傳遞的(HTTPS不是),帶來的安全性問題還是很大的
C.網絡負擔:我們知道cookie會被附加在每個HTTP請求中,請求和返回都會有,浪費流量

  1. 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();
      }
      }
  2. 參數說明
    name: 名稱
    value: 值
    path:路徑
    expires:過期時間,如果不設置過期時間,關閉瀏覽器消失,如果設置過期時間存到硬盤中,默認存在內存中
    secure: 安全
    domain:web 共享cookie

  3. 適用場景

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 變化

image.png
  1. 使用場景

保存少量業務數據,比如用戶信息,但是需要加密

  1. 怎么設計一個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語句

  1. 支持情況
    Web SQL 數據庫可以在最新版的 Safari, Chrome 和 Opera 瀏覽器中工作

  2. 核心API
    openDatabase:新建的數據庫創建一個數據庫對象或者打開已有的數據庫
    transaction: 執行SQL語句的事務
    executeSql: 執行SQL語句

3.存儲大小: 自定義設置大小

  1. 使用場景:對數據庫設計比較了解的人,存儲大量數據

  2. 怎么設計存儲工具類

 /**
 * 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);
}
}

結果:

image.png
四. indexDB - 索引數據庫(可以用保存數據/離線緩存)
  1. 特點:

A. 異步API-請求-響應的模式保存與獲取數據,不是我們熟悉的key-value,但是存儲方式鍵值對儲存;
B.索引數據庫沒有表的概念,但是有objectStore, 就是類似于數據庫的表
C.一個數據庫中可以包含多個objectStore
D. 不屬于關系型數據庫(不支持 SQL 查詢語句)
E. 儲存空間大 IndexedDB 的儲存空間比 LocalStorage 大得多,一般來說不少于 250MB,甚至沒有上限
F.同源限制 IndexedDB 受到同源限制,每一個數據庫對應創建它的域名。網頁只能訪問自身域名下的數據庫,而不能訪問跨域的數據庫

2.對象的基本概念

數據庫:IDBDatabase 對象
對象倉庫:IDBObjectStore 對象
索引: IDBIndex 對象
事務: IDBTransaction 對象
操作請求:IDBRequest 對象
指針: IDBCursor 對象
主鍵集合:IDBKeyRange 對象

  1. 操作流程

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);

結果:

image.png

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);
}
  1. 使用場景

存儲大量數據,比如頁面離線緩存等

最后,以上總結都是老夫為了方便理解自己總結的內容,其中參考以下資料,創作不易,尊重原著

參考文獻鏈接
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-瀏覽器緩存機制

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380