1. 程式人生 > >前端開發的客戶端本地儲存

前端開發的客戶端本地儲存

4

在前端開發過程中,為了與伺服器更方便的互動或者提升使用者體驗,我們都會在客戶端(使用者)本地儲存一部分資料,比如cookie/localStorage/sessionStorage。在後端管理系統的前端,更是會涉及到一部分超大資料的請求,一個介面有時會達到5M甚至15M的程度,當這個介面資料並不是經常更新時,我們可以用兩種方式,一種是分頁請求+預載入+懶載入,另一種就是本地儲存+熱更新。而由於第二種方式使用者體驗更優秀,便是我常用的方式。

這篇文章的客戶端本地儲存,我們主要講到cookie/localStorage/sessionStorage/indexedDB四種技術。

cookie

HTTP Cookie

通常簡稱cookie,該標準用於瀏覽器儲存會話資訊,在發起HTTP請求時攜帶Cookie引數:

// Request Header 

GET /oss/index.php?r=api/jlog/collect HTTP/1.1
Host: gzhxy.baidu.com:8090
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36

···

Cookie: name=Leon; age=24

Cookie有一些限制:

  • 它是以(分號+一個空格)分割的鍵值對字串,在網路傳送時必須是URL編碼的。
  • 繫結在固定域名下,不允許其它域名訪問。
  • 有些瀏覽器有數量限制,在超出後刪除順序不統一,有些最近最少使用(LRU),有些隨機刪
  • 同一域名下總大小限制5KB,超出限制後靜默失敗

Cookie的引數構成:

  • 名稱:唯一確定,不區分大小寫(實際編寫建議區分),必須URL編碼
  • 值:字串值,必須URL編碼
  • 域:該cookie欄位有效的域,可以為baidu.com域名,也可以為cdn.baidu.com子域名,預設值為當前頁面子域名
  • 路徑:該cookie欄位有效範圍為指定域下的具體路徑,如cdn.baidu.com/oss
    ,那麼其他路徑就無法訪問
  • 失效時間:cookie被刪除的時間戳,預設值為瀏覽器會話結束被刪除,也可以手動設定,時間格式為GMT格式Wdy, DD-Mon-YYYY HH:MM:SS GMT,可以呼叫Date例項的toGMTString()方法轉換,如果設定為過去的時間,該cookie被立刻刪除
  • 安全標誌:為單詞secure而非鍵值對,指定後,只有在SSL連線的時候才會被髮送到伺服器,也就是https協議

注意引數中只有名稱和值才會被髮送給伺服器,其餘的只是需要瀏覽器識別的命令式引數。

cookie的介面設定非常的不人性化,往往需要我們對其操作進行封裝才會方便使用。下面我們對其進行增刪查改。

檢視cookie:decodeURIComponent(document.cookie);

新增或修改cookie:

// 需要改成自己需要的cookie和域名、路徑以及是否為https
document.cookie = 'encodeURIComponent(name)=encodeURIComponent(Leon); expries=' + (new Date(Date.now() + 24*60*60*1000)).toGMTString() + '; path=oss; domain=cdn.baidu.com; secure';

刪除cookie: document.cookie = 'name=Leon; expires=' + (new Date(0)).toGMTString();

具體的封裝的方式網上有很多,可以去搜一搜,核心就是對cookie進行字串檢索和切分,以及將傳入的函式引數最終轉換為字串。

Storage

由於cookie的大小限制和需要全量傳遞給伺服器,在很多場景下並不適用,所以HTML5規範中出現了Storage物件,包含localStoragesessionStorage兩種繼承物件,屬於window的屬性。它提供了通常5M的大小空間來儲存無需伺服器互動的本地資料。

Storage的常用方法:

  • clear(): 清除所有值
  • getItem(name): 獲取指定name的值
  • key(index): 獲得對應索引值的鍵名
  • removeItem(name): 刪除指定name的鍵值對
  • setItem(name, value): 為指定name設定對應的值

除了這些方法之外,Storage物件可以直接通過點語法或者方括號語法訪問屬性和操作屬性,也可以通過delete關鍵字刪除屬性。該物件中的值均為字串。

持久化資料localStorage通常保留到JS刪除或者使用者清除瀏覽器快取。

會話資料sessionStorage保留到關閉瀏覽器。由於繫結會話視窗,所以不支援本地檔案讀寫。另外在IE8中,該物件為非同步讀寫,需要呼叫begin()commit()方法保證成功讀取,不再贅述。

Storage物件做任何的新增,修改或者刪除操作,都會觸發storage事件。該事件只支援在與服務端通訊時,一個頁面修改,另一個頁面會觸發該事件。

document.addEventListener('storage', function (e) {
    console.log(e);
});

該事件物件的主要屬性:

  • domain: 發生變化的儲存空間的域名
  • key:修改的鍵名
  • newValue:如果是設定值,則為新值;如果刪除,則為null
  • oldValue:更改前的值

IndexedDB

擁有了Storage利器,已經能解決很多問題,但是通常5M的大小限制還是會限制一部分場景,比如後臺管理系統的介面資料很容易突破5M,這個時候就需要我們的瀏覽器資料庫IndexedDB了。其實在此之前,各家廠商主要推廣的是Web SQL Database,不過後來被廢棄了,雖然現在在部分平臺上也能使用,但我們不做介紹了。

IndexedDB資料庫用於瀏覽器儲存結構化資料,區別於傳統資料庫它儲存的是物件,完全採用事務型別,所有的操作被轉換為請求的方式,所以我們需要對每一步操作添加回調函式。

一個完整的例項為:

// 判斷能否正確開啟資料庫,避免多次檢測
let dbOpened = false;
// 開啟本地持久化資料庫,預設版本為1
const request = indexedDB.open('jomocha');
// 當開啟錯誤時
request.onerror = function(event){
    console.error('開啟本地持久化資料錯誤', event);
    OSS.commonUI.showMsg('開啟本地持久化資料庫錯誤,試用功能,不影響使用,請聯絡zhaoshuaiqiang', 'error');
};
// 當資料庫首次建立該版本時(首次建立或更新版本)
request.onupgradeneeded = function(event){
    const db = event.target.result;
    // 建立一個數據庫儲存物件,儲存所有的維度項,分為name和list兩個屬性
    const objectStore = db.createObjectStore('dimensions', {
        keyPath: 'name'
    });
    // 定義儲存物件的資料項屬性
    objectStore.createIndex('name', 'name', {
        unique: true
    });
};
// 成功打開了資料庫
request.onsuccess = function(event){
    dbOpened = true;
    const db = event.target.result;
    // 新建一個事務,包含oncomplete 和onerror控制代碼事件,預設值為readonly,只讀模式,可並行
    const transaction = db.transaction(['dimensions']);
    // 開啟儲存物件
    const objectStore = transaction.objectStore('dimensions');
    const request = objectStore.get('host');
    request.onsuccess = function (event) {
        // 第一次開啟資料庫時,肯定沒有資料,所以需要檢測
        if (event.target.result) {
            JomoCha.data = event.target.result.list;
        }
    };
}

資料庫

當我們需要使用IndexedDB時,首先要呼叫indexedDB.open()方法開啟資料庫,如果該資料存在,則發起開啟的請求,如果不存在,則發起建立並開啟的請求。該方法會返回一個IDBRequest物件,可以在該物件上添加回調方法。具體的方法如示例中的最外層請求。

回撥函式傳入的事件屬性event.target就指向該請求,即request。如果發生了錯誤,event.target.errorCode將會儲存錯誤資訊的錯誤碼;如果成功,event.target.result就會儲存一個數據庫例項物件。

錯誤碼列表(第二個開始省略字首IDBDatabaseException.):

  • IDBDatabaseException.UNKNOWN_ERR(1):意外錯誤,無法歸類
  • NON_TRANSIENT_ERR(2):操作不合法
  • NOT_FOUND_ERR(3):未發現要操作的資料庫
  • CONSTRAINT_ERR(4):違反了資料庫約束
  • DATA_ERR(5):提供給事務的資料不滿足要求
  • NOT_ALLOWED_ERR(6):操作不合法
  • TRANSACTION_INACTIVE_ERR(7):試圖重用已完成的事務
  • ABORT_ERR(8):請求中斷,未完成
  • READ_ONLY_ERR(9):試圖在只讀模式下寫入或修改資料
  • TIMEOUT_ERR(10):在有效時間內未完成操作
  • QUOTA_ERR(11):磁碟空間不足

物件儲存空間(表)

成功開啟資料庫之後,我們就可以開啟物件儲存空間了,你可以把它理解成資料庫中的表,用於儲存不同的資料,如使用者、交易、購物車等。下面的所有db代表成功回撥中的資料庫物件.

我們先呼叫db.createObjectStore('dimensions', {keyPath:'name'});來建立一張表,這個dimensions為唯一表名,其中的keyPath屬性指定該表的鍵名,後面儲存的所有資料都必須擁有該屬性。

看下寫入資料庫的例項:

// 每次同步更新最新資料
$.ajax({
    url: '?r=tools/api/hosts',
    success: function (data) {
        // 如果能夠開啟本地資料庫,則儲存
        if (dbOpened) {
            const request = indexedDB.open('jomocha');
            request.onsuccess = function(event){
                const db = event.target.result;
                // 新建一個事務,讀寫模式,不可並行
                const transaction = db.transaction(['dimensions'], 'readwrite');
                // 開啟儲存物件
                const objectStore = transaction.objectStore('dimensions');
                // 使用put方法,有則修改,沒有則新增
                const request = objectStore.put({
                    name: 'host',
                    list: data.data
                });
            }
        }
    }
});

這裡寫入資料庫的物件裡就包含了name引數。我們拿到資料使用add()put()方法新增資料,這兩種方法區別在於遇到相同的鍵名存在時,add()報錯,put()修改原有值。這兩種方法依然為請求,可以對其指定onsuccess()onerror()事件處理回撥,示例中省略了。

事務

在建立完成資料後,就可以對其進行查詢了,indexedDB中所有讀寫操作都要通過事務,我們呼叫db.transaction();方法來開啟所有儲存空間(表),也可以傳入引數來控制我們需要開啟的表:

// 開啟一張表
db.transaction('user');
// 開啟多張表
db.transaction(['user', 'dimensions']);

該方法還接受第二個引數作為訪問方式,包含3種:readonly(預設值)、readwriteversionchange。最後一個比較特別,為在版本更新時使用,不可以與其他事務併發執行,允許任何操作,包括刪除和建立索引。

現在通過事務我們已經確定了操作空間,接下來就可以獲取具體的資料物件了,下面的transaction代表著上面的db.transaction()方法返回的事務。事務可以執行多個請求,本身也是一個請求,可以指定相應的oncomplete()onerror()事件處理回撥。其中的oncomplete()事件不能拿到該事務中請求的資料。

使用transaction.objectStore('dimension');獲取資料物件,然後就可以通過add/put/get/delete/clear方法進行增刪查改,均為請求,需要設定onsuccess()onerror()回撥。

遊標遍歷

我們可以使用get()方法檢索出具體的單個物件,但是如果需要遍歷,我們就要使用到遊標查詢了,也就是指定範圍,然後遍歷資料。下面的objectStore指代上面transaction.objectStore()返回的資料物件集。

// 指定遊標範圍
const cursorRange = IDBKeyRange.bound('001', '100');
// 開啟遊標查詢
const request = objectStore.openCursor(cursorRange);
request.onsuccess = function (event) {
    const cursor = event.target.result;
    // 必須檢查cursor是否存在,如果該項存在,則為IDBCursor例項,否則為null
    if (cursor) {
        console.log(cursor.key + ': ' + cursor.value);
        cursor.continue();
    } else {
        console.log('遍歷完成');
    }
}
request.onerror = function (event) {
    console.error('遊標區間獲取失敗');
}

在遊標遍歷時,具體的資料會儲存在event.target.result裡,如果該項存在,則會為一個IDBCursor物件例項,否則為null。例項的屬性:

  • direction:數值,表示遊標的方向,next/nextunique/prev/prevunique,帶有unique的會去重
  • key:資料物件鍵
  • value:資料物件值
  • primaryKey:遊標當前的使用鍵值,後面會說

在遍歷到遊標中具體的每一項時,可以使用update()delete()來修改,如果想要移動遊標:

  • continue(key):存在key移動到指定項,否則下一項
  • advance(count):存在count移動指定項數,否則上一項

我們通過IDBKeyRange物件來控制鍵範圍,有4種方式指定:

  • only(key):只取得想要的鍵值對,等同於直接呼叫get(key)
  • lowerBound(key, true):第一個元素為key,如果第二個引數為true,從該項的下一項開始,預設值為false
  • upperBound(key, true):最後一個元素為key,如果第二個引數為true,從該項的下一項開始,預設值為false
  • bound(lowerkey, upperkey, true, true):前兩者的結合,1和3對應lower,2和4對應upper

openCursor()方法接收的第一個引數為範圍區間,如果為null,則預設全部範圍;第二個引數為方向,為next/nextunique/prev/prevunique四個,帶有unique的會去重

索引

可以使用objectStore.createIndex('name', 'name', {unique: true});來建立索引,分別為索引名,索引的屬性名,該屬性值是否唯一。呼叫objectStore.delete('name');來刪除索引。刪除索引不會影響資料,所以沒有回撥函式。

如果我們不想在主鍵上遍歷遊標或者獲取資料,可以在資料集上取得新的索引列表:

// 直接切換索引
const index = objectStore('dimensions');
// 在該索引上進行遊標遍歷
request = index.openCursor();

如果我們在非主鍵的遊標中,想要去的主鍵值,呼叫index.getKey('007');

併發問題

資料庫操作存在併發問題,但由於是非同步,我們不用擔心,但是如果存在新版本變更還是會導致問題。所以開啟資料庫時指定onversionchange()處理事件,可以避免這個問題,在setVesion()時,如果觸發onerror()代表已經打開了該資料庫,無法現在更新版本,提示使用者關閉其它網頁,重新呼叫更新。

總結

如果需要與伺服器實時互動,使用cookie,如果需要儲存一些小資訊欄位,使用localStorage,如果只需要本次會話有效,使用sessionStorage,如果資料很大,使用indexedDB。使用什麼技術跟業務場景匹配,但是技術還是都要了解都要會,畢竟,巧婦難為無米之炊。

參考資料

相關推薦

前端開發客戶本地儲存

4 在前端開發過程中,為了與伺服器更方便的互動或者提升使用者體驗,我們都會在客戶端(使用者)本地儲存一部分資料,比如cookie/localStorage/sessionStorage。在後端管理系統的前端,更是會涉及到一部分超大資料的請求,一個介面有時會達到5M甚至15M

C#開發WEBService服務 C++開發客戶調用WEBService服務

blank 內容 目錄 www nal .net 操作 service服務 3.1 編寫程序 http://blog.csdn.net/u011835515/article/details/47615425 遇到問題及解決方法: HTTP 錯誤 500.19- I

maven版axis2調用cxf服務開發客戶(三)

param system axis2 測試 log png 依賴 println dep 一、新建一個maven項目 二、pom.xml引入axis2依賴 <dependency> <groupId>org.apache.axis2<

Java開發後臺介面+Android開發客戶的一個例項(學生成績管理)(一)

本文主要是一個查詢學生成績的一整套系統,主要為功能實現,串聯起來後端和移動端的連線。UI後期可以根據自己的愛好再調整,這裡分為兩部分進行介紹: 1、使用Java開發後臺介面,使用現如今非常流行的SpringBoot框架,使用MySql資料庫,持久層框架使用MyBatis,後臺開發平臺為idea,

js獲取客戶本地ip

使用js獲取客戶端本地ip,不需要額外引入別的檔案 【注:若IE瀏覽器不進行安全設定,IE瀏覽器會預設攔截ActiveX控制元件的使用,將不會返回客戶端的IP地址】 下邊的程式碼使用於通用瀏覽器,但是IE瀏覽器必須去設定ActiveX控制元件。 <!DOCTYPE html> <h

使用BindingX開發客戶炫酷動畫

Weex 是一套簡單易用的跨平臺開發方案,能以 web 的開發體驗構建高效能、可擴充套件的 native 應用,為了做到這些,Weex 與 Vue 合作,使用 Vue 作為上層框架,並遵循 W3C 標準實現了統一的 JSEngine 和 DOM API,這樣一來

2019年Web程式設計師路線圖:前端開發+後開發+DevOps

Web開發人員路線圖已經連續釋出三年,由Github平臺的kamranahmedse倡導,2019版本彙集了19位貢獻者。跟往年一樣,技術思維導圖涵蓋前端開發+後端開發+DevOps,話不多說,直接上圖。 想要學習Web前端的小夥伴,在這推薦下自己的Web學習群:585843

2019年Web程式設計師最新路線圖:前端開發+後開發+DevOps

Web開發人員路線圖已經連續釋出三年,由Github平臺的kamranahmedse倡導,2019版本彙集了19位貢獻者。跟往年一樣,技術思維導圖涵蓋前端開發+後端開發+DevOps,話不多說,直接上圖。   在這裡我還是要推薦下我自己建的web前端開發學習裙:659

前端客戶渲染 vs 伺服器渲染

渲染工作應該由誰完成? 時下,前端 UI 設計越來越複雜,可謂“XX與XX齊飛,XX共XX一色”。 越來越複雜的 UI 意味著越來越重的 渲染工作。 目前通常有兩種選擇:伺服器渲染 與 客戶端渲染 筆者是支援客戶端渲染的(沒錯就是欽點的) 以

使用js獲取客戶本地ip,不需要額外引入別的檔案

網上找了好久,一直沒找到,後來翻牆谷歌找到的直接貼程式碼:<script type="text/javascript">function getUserIP(onNewIP) { //  onNewIp - your listener function for n

前端開發手機呼叫開發者工具(控制檯資訊,報錯,介面資訊等),ios安卓通用vue-cli專案中mockjs和vConsole的使用

在vue-cli專案中mockjs和vConsole的使用 mockjs使用 1.安裝npm install mockjs 2.在src/assets目錄下建立個util資料夾,並在裡新建一個檔案mock.js 如下: const Mock = require(‘m

原生js獲取客戶本地ip

function getUserIP(onNewIP) { // onNewIp - your listener function for new IPs //compatibility for firefox and chrome var myPeerConnection = w

社交app應用開發 客戶+伺服器原始碼

通過前段時間十來天的ios學習,前幾天抽空完成了這個APP,可能問題很多,希望大家諒解。 同時又什麼程式碼的問題,希望大家給提出建議。 本程式是仿照Appstore上的一個應用來參考做的。 程式架構,客戶端(ios)+服務端(java)+Tomcate伺服器+mysql資料庫。 由於時間有限,很多細節方面的東

(Lua) 客戶本地時間戳和伺服器時間戳不一致的解決方案

問題來源:伺服器的時間是固定的,國內一般都設定為北京時間(東8時區的時間),而客戶端分佈在世界各地,客戶端系統設定的時區是不固定的。很多時候在設計時,沒有考慮時區不一致的情況,直接使用時間戳來進行時間轉換和比較,往往出現很多預想不到的問題。基本概念/方法:1、【時區】:全世界

客戶資料儲存cookie、localStoeage、sessionStorage(小記)

一、資料儲存分為客戶端儲存和服務端儲存1、而對於客戶端儲存,在html5以前只能通過cookie來實現;html 5以後增加了web儲存(實際儲存本地)的功能,(1)對於web儲存有兩個標準:a、File API 標準: 支援該標準的瀏覽器能夠計算機硬碟的其他檔案中讀取資料b

前端開發移動注意事項

一、meta的使用1、<meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"

客戶圖片儲存到資料庫中的方法

核心內容: (1)使用到兩個函式模組(FM):SCMS_BINARY_TO_XSTRING 和 SCMS_XSTRING_TO_BINARY; (2)資料庫儲存圖片的欄位設為 RAWSTRING型別(對應於ABAP資料型別XSTRING)。 一、假設場景及前提 (1)將客戶端C盤下的“PIC.JPG”檔案儲

vue開發:生成token儲存客戶localStorage中

前面我們已經瞭解了可以通過localStorage在客戶端(瀏覽器)儲存資料。 回顧token 框架中的RESTful api快速領悟(中):token認證 框架中的RESTful api快速領悟(下):token的設定 我們後端有這樣一個介面: http://lo

七牛雲端儲存客戶(本人開發,開源)

直接不廢話,開源地址:https://github.com/wjs5943283/QiNiuBucketClientQiNiu bucket Client 簡單的七牛儲存客戶端,增加了批量下載功能, 使用wpf 基於 .net framework 4.0 下載Dubug.zip直接使用,填寫AK和S

TCP客戶圖片上傳服務儲存本地示例

//TCP客戶端public class TCPClient { public static void main(String[] args)throws IOException { Socket socket = new Socket("127.0.0.1",8888)