摘要:在前端開發(fā)過程中,為了與服務(wù)器更方便的交互或者提升用戶體驗(yàn),我們都會(huì)在客戶端用戶本地保存一部分?jǐn)?shù)據(jù),比如。這篇文章的客戶端本地存儲(chǔ),我們主要講到四種技術(shù)?;卣{(diào)函數(shù)傳入的事件屬性就指向該請(qǐng)求,即。刪除索引不會(huì)影響數(shù)據(jù),所以沒有回調(diào)函數(shù)。
在前端開發(fā)過程中,為了與服務(wù)器更方便的交互或者提升用戶體驗(yàn),我們都會(huì)在客戶端(用戶)本地保存一部分?jǐn)?shù)據(jù),比如cookie/localStorage/sessionStorage。在后端管理系統(tǒng)的前端,更是會(huì)涉及到一部分超大數(shù)據(jù)的請(qǐng)求,一個(gè)接口有時(shí)會(huì)達(dá)到5M甚至15M的程度,當(dāng)這個(gè)接口數(shù)據(jù)并不是經(jīng)常更新時(shí),我們可以用兩種方式,一種是分頁(yè)請(qǐng)求+預(yù)加載+懶加載,另一種就是本地存儲(chǔ)+熱更新。而由于第二種方式用戶體驗(yàn)更優(yōu)秀,便是我常用的方式。
這篇文章的客戶端本地存儲(chǔ),我們主要講到cookie/localStorage/sessionStorage/indexedDB四種技術(shù)。
cookieHTTP Cookie通常簡(jiǎn)稱cookie,該標(biāo)準(zhǔn)用于瀏覽器存儲(chǔ)會(huì)話信息,在發(fā)起HTTP請(qǐng)求時(shí)攜帶Cookie參數(shù):
// 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有一些限制:
它是以; (分號(hào)+一個(gè)空格)分割的鍵值對(duì)字符串,在網(wǎng)絡(luò)傳送時(shí)必須是URL編碼的。
綁定在固定域名下,不允許其它域名訪問。
有些瀏覽器有數(shù)量限制,在超出后刪除順序不統(tǒng)一,有些最近最少使用(LRU),有些隨機(jī)刪
同一域名下總大小限制5KB,超出限制后靜默失敗
Cookie的參數(shù)構(gòu)成:
名稱:唯一確定,不區(qū)分大小寫(實(shí)際編寫建議區(qū)分),必須URL編碼
值:字符串值,必須URL編碼
域:該cookie字段有效的域,可以為baidu.com域名,也可以為cdn.baidu.com子域名,缺省值為當(dāng)前頁(yè)面子域名
路徑:該cookie字段有效范圍為指定域下的具體路徑,如cdn.baidu.com/oss,那么其他路徑就無法訪問
失效時(shí)間:cookie被刪除的時(shí)間戳,缺省值為瀏覽器會(huì)話結(jié)束被刪除,也可以手動(dòng)設(shè)置,時(shí)間格式為GMT格式Wdy, DD-Mon-YYYY HH:MM:SS GMT,可以調(diào)用Date實(shí)例的toGMTString()方法轉(zhuǎn)換,如果設(shè)置為過去的時(shí)間,該cookie被立刻刪除
安全標(biāo)志:為單詞secure而非鍵值對(duì),指定后,只有在SSL連接的時(shí)候才會(huì)被發(fā)送到服務(wù)器,也就是https協(xié)議
注意參數(shù)中只有名稱和值才會(huì)被發(fā)送給服務(wù)器,其余的只是需要瀏覽器識(shí)別的命令式參數(shù)。
cookie的接口設(shè)置非常的不人性化,往往需要我們對(duì)其操作進(jìn)行封裝才會(huì)方便使用。下面我們對(duì)其進(jìn)行增刪查改。
查看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();
具體的封裝的方式網(wǎng)上有很多,可以去搜一搜,核心就是對(duì)cookie進(jìn)行字符串檢索和切分,以及將傳入的函數(shù)參數(shù)最終轉(zhuǎn)換為字符串。
Storage由于cookie的大小限制和需要全量傳遞給服務(wù)器,在很多場(chǎng)景下并不適用,所以HTML5規(guī)范中出現(xiàn)了Storage對(duì)象,包含localStorage和sessionStorage兩種繼承對(duì)象,屬于window的屬性。它提供了通常5M的大小空間來保存無需服務(wù)器交互的本地?cái)?shù)據(jù)。
Storage的常用方法:
clear(): 清除所有值
getItem(name): 獲取指定name的值
key(index): 獲得對(duì)應(yīng)索引值的鍵名
removeItem(name): 刪除指定name的鍵值對(duì)
setItem(name, value): 為指定name設(shè)置對(duì)應(yīng)的值
除了這些方法之外,Storage對(duì)象可以直接通過點(diǎn)語(yǔ)法或者方括號(hào)語(yǔ)法訪問屬性和操作屬性,也可以通過delete關(guān)鍵字刪除屬性。該對(duì)象中的值均為字符串。
持久化數(shù)據(jù)localStorage通常保留到JS刪除或者用戶清除瀏覽器緩存。
會(huì)話數(shù)據(jù)sessionStorage保留到關(guān)閉瀏覽器。由于綁定會(huì)話窗口,所以不支持本地文件讀寫。另外在IE8中,該對(duì)象為異步讀寫,需要調(diào)用begin()和commit()方法保證成功讀取,不再贅述。
對(duì)Storage對(duì)象做任何的新增,修改或者刪除操作,都會(huì)觸發(fā)storage事件。該事件只支持在與服務(wù)端通信時(shí),一個(gè)頁(yè)面修改,另一個(gè)頁(yè)面會(huì)觸發(fā)該事件。
document.addEventListener("storage", function (e) { console.log(e); });
該事件對(duì)象的主要屬性:
domain: 發(fā)生變化的存儲(chǔ)空間的域名
key:修改的鍵名
newValue:如果是設(shè)置值,則為新值;如果刪除,則為null
oldValue:更改前的值
IndexedDB擁有了Storage利器,已經(jīng)能解決很多問題,但是通常5M的大小限制還是會(huì)限制一部分場(chǎng)景,比如后臺(tái)管理系統(tǒng)的接口數(shù)據(jù)很容易突破5M,這個(gè)時(shí)候就需要我們的瀏覽器數(shù)據(jù)庫(kù)IndexedDB了。其實(shí)在此之前,各家廠商主要推廣的是Web SQL Database,不過后來被廢棄了,雖然現(xiàn)在在部分平臺(tái)上也能使用,但我們不做介紹了。
IndexedDB數(shù)據(jù)庫(kù)用于瀏覽器保存結(jié)構(gòu)化數(shù)據(jù),區(qū)別于傳統(tǒng)數(shù)據(jù)庫(kù)它保存的是對(duì)象,完全采用事務(wù)類型,所有的操作被轉(zhuǎn)換為請(qǐng)求的方式,所以我們需要對(duì)每一步操作添加回調(diào)函數(shù)。
一個(gè)完整的實(shí)例為:
// 判斷能否正確打開數(shù)據(jù)庫(kù),避免多次檢測(cè) let dbOpened = false; // 打開本地持久化數(shù)據(jù)庫(kù),默認(rèn)版本為1 const request = indexedDB.open("jomocha"); // 當(dāng)打開錯(cuò)誤時(shí) request.onerror = function(event){ console.error("打開本地持久化數(shù)據(jù)錯(cuò)誤", event); OSS.commonUI.showMsg("打開本地持久化數(shù)據(jù)庫(kù)錯(cuò)誤,試用功能,不影響使用,請(qǐng)聯(lián)系z(mì)haoshuaiqiang", "error"); }; // 當(dāng)數(shù)據(jù)庫(kù)首次創(chuàng)建該版本時(shí)(首次創(chuàng)建或更新版本) request.onupgradeneeded = function(event){ const db = event.target.result; // 創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)存儲(chǔ)對(duì)象,保存所有的維度項(xiàng),分為name和list兩個(gè)屬性 const objectStore = db.createObjectStore("dimensions", { keyPath: "name" }); // 定義存儲(chǔ)對(duì)象的數(shù)據(jù)項(xiàng)屬性 objectStore.createIndex("name", "name", { unique: true }); }; // 成功打開了數(shù)據(jù)庫(kù) request.onsuccess = function(event){ dbOpened = true; const db = event.target.result; // 新建一個(gè)事務(wù),包含oncomplete 和onerror句柄事件,缺省值為readonly,只讀模式,可并行 const transaction = db.transaction(["dimensions"]); // 打開存儲(chǔ)對(duì)象 const objectStore = transaction.objectStore("dimensions"); const request = objectStore.get("host"); request.onsuccess = function (event) { // 第一次打開數(shù)據(jù)庫(kù)時(shí),肯定沒有數(shù)據(jù),所以需要檢測(cè) if (event.target.result) { JomoCha.data = event.target.result.list; } }; }數(shù)據(jù)庫(kù)
當(dāng)我們需要使用IndexedDB時(shí),首先要調(diào)用indexedDB.open()方法打開數(shù)據(jù)庫(kù),如果該數(shù)據(jù)存在,則發(fā)起打開的請(qǐng)求,如果不存在,則發(fā)起創(chuàng)建并打開的請(qǐng)求。該方法會(huì)返回一個(gè)IDBRequest對(duì)象,可以在該對(duì)象上添加回調(diào)方法。具體的方法如示例中的最外層請(qǐng)求。
回調(diào)函數(shù)傳入的事件屬性event.target就指向該請(qǐng)求,即request。如果發(fā)生了錯(cuò)誤,event.target.errorCode將會(huì)保存錯(cuò)誤信息的錯(cuò)誤碼;如果成功,event.target.result就會(huì)保存一個(gè)數(shù)據(jù)庫(kù)實(shí)例對(duì)象。
錯(cuò)誤碼列表(第二個(gè)開始省略前綴IDBDatabaseException.):
IDBDatabaseException.UNKNOWN_ERR(1):意外錯(cuò)誤,無法歸類
NON_TRANSIENT_ERR(2):操作不合法
NOT_FOUND_ERR(3):未發(fā)現(xiàn)要操作的數(shù)據(jù)庫(kù)
CONSTRAINT_ERR(4):違反了數(shù)據(jù)庫(kù)約束
DATA_ERR(5):提供給事務(wù)的數(shù)據(jù)不滿足要求
NOT_ALLOWED_ERR(6):操作不合法
TRANSACTION_INACTIVE_ERR(7):試圖重用已完成的事務(wù)
ABORT_ERR(8):請(qǐng)求中斷,未完成
READ_ONLY_ERR(9):試圖在只讀模式下寫入或修改數(shù)據(jù)
TIMEOUT_ERR(10):在有效時(shí)間內(nèi)未完成操作
QUOTA_ERR(11):磁盤空間不足
對(duì)象存儲(chǔ)空間(表)成功打開數(shù)據(jù)庫(kù)之后,我們就可以打開對(duì)象存儲(chǔ)空間了,你可以把它理解成數(shù)據(jù)庫(kù)中的表,用于保存不同的數(shù)據(jù),如用戶、交易、購(gòu)物車等。下面的所有db代表成功回調(diào)中的數(shù)據(jù)庫(kù)對(duì)象.
我們先調(diào)用db.createObjectStore("dimensions", {keyPath:"name"});來創(chuàng)建一張表,這個(gè)dimensions為唯一表名,其中的keyPath屬性指定該表的鍵名,后面存儲(chǔ)的所有數(shù)據(jù)都必須擁有該屬性。
看下寫入數(shù)據(jù)庫(kù)的實(shí)例:
// 每次同步更新最新數(shù)據(jù) $.ajax({ url: "?r=tools/api/hosts", success: function (data) { // 如果能夠打開本地?cái)?shù)據(jù)庫(kù),則保存 if (dbOpened) { const request = indexedDB.open("jomocha"); request.onsuccess = function(event){ const db = event.target.result; // 新建一個(gè)事務(wù),讀寫模式,不可并行 const transaction = db.transaction(["dimensions"], "readwrite"); // 打開存儲(chǔ)對(duì)象 const objectStore = transaction.objectStore("dimensions"); // 使用put方法,有則修改,沒有則添加 const request = objectStore.put({ name: "host", list: data.data }); } } } });
這里寫入數(shù)據(jù)庫(kù)的對(duì)象里就包含了name參數(shù)。我們拿到數(shù)據(jù)使用add()或put()方法添加數(shù)據(jù),這兩種方法區(qū)別在于遇到相同的鍵名存在時(shí),add()報(bào)錯(cuò),put()修改原有值。這兩種方法依然為請(qǐng)求,可以對(duì)其指定onsuccess()或onerror()事件處理回調(diào),示例中省略了。
事務(wù)在創(chuàng)建完成數(shù)據(jù)后,就可以對(duì)其進(jìn)行查詢了,indexedDB中所有讀寫操作都要通過事務(wù),我們調(diào)用db.transaction();方法來打開所有存儲(chǔ)空間(表),也可以傳入?yún)?shù)來控制我們需要打開的表:
// 打開一張表 db.transaction("user"); // 打開多張表 db.transaction(["user", "dimensions"]);
該方法還接受第二個(gè)參數(shù)作為訪問方式,包含3種:readonly(缺省值)、readwrite、versionchange。最后一個(gè)比較特別,為在版本更新時(shí)使用,不可以與其他事務(wù)并發(fā)執(zhí)行,允許任何操作,包括刪除和創(chuàng)建索引。
現(xiàn)在通過事務(wù)我們已經(jīng)確定了操作空間,接下來就可以獲取具體的數(shù)據(jù)對(duì)象了,下面的transaction代表著上面的db.transaction()方法返回的事務(wù)。事務(wù)可以執(zhí)行多個(gè)請(qǐng)求,本身也是一個(gè)請(qǐng)求,可以指定相應(yīng)的oncomplete()和onerror()事件處理回調(diào)。其中的oncomplete()事件不能拿到該事務(wù)中請(qǐng)求的數(shù)據(jù)。
使用transaction.objectStore("dimension");獲取數(shù)據(jù)對(duì)象,然后就可以通過add/put/get/delete/clear方法進(jìn)行增刪查改,均為請(qǐng)求,需要設(shè)置onsuccess()和onerror()回調(diào)。
游標(biāo)遍歷我們可以使用get()方法檢索出具體的單個(gè)對(duì)象,但是如果需要遍歷,我們就要使用到游標(biāo)查詢了,也就是指定范圍,然后遍歷數(shù)據(jù)。下面的objectStore指代上面transaction.objectStore()返回的數(shù)據(jù)對(duì)象集。
// 指定游標(biāo)范圍 const cursorRange = IDBKeyRange.bound("001", "100"); // 打開游標(biāo)查詢 const request = objectStore.openCursor(cursorRange); request.onsuccess = function (event) { const cursor = event.target.result; // 必須檢查cursor是否存在,如果該項(xiàng)存在,則為IDBCursor實(shí)例,否則為null if (cursor) { console.log(cursor.key + ": " + cursor.value); cursor.continue(); } else { console.log("遍歷完成"); } } request.onerror = function (event) { console.error("游標(biāo)區(qū)間獲取失敗"); }
在游標(biāo)遍歷時(shí),具體的數(shù)據(jù)會(huì)保存在event.target.result里,如果該項(xiàng)存在,則會(huì)為一個(gè)IDBCursor對(duì)象實(shí)例,否則為null。實(shí)例的屬性:
direction:數(shù)值,表示游標(biāo)的方向,next/nextunique/prev/prevunique,帶有unique的會(huì)去重
key:數(shù)據(jù)對(duì)象鍵
value:數(shù)據(jù)對(duì)象值
primaryKey:游標(biāo)當(dāng)前的使用鍵值,后面會(huì)說
在遍歷到游標(biāo)中具體的每一項(xiàng)時(shí),可以使用update()和delete()來修改,如果想要移動(dòng)游標(biāo):
continue(key):存在key移動(dòng)到指定項(xiàng),否則下一項(xiàng)
advance(count):存在count移動(dòng)指定項(xiàng)數(shù),否則上一項(xiàng)
我們通過IDBKeyRange對(duì)象來控制鍵范圍,有4種方式指定:
only(key):只取得想要的鍵值對(duì),等同于直接調(diào)用get(key)
lowerBound(key, true):第一個(gè)元素為key,如果第二個(gè)參數(shù)為true,從該項(xiàng)的下一項(xiàng)開始,缺省值為false
upperBound(key, true):最后一個(gè)元素為key,如果第二個(gè)參數(shù)為true,從該項(xiàng)的下一項(xiàng)開始,缺省值為false
bound(lowerkey, upperkey, true, true):前兩者的結(jié)合,1和3對(duì)應(yīng)lower,2和4對(duì)應(yīng)upper
openCursor()方法接收的第一個(gè)參數(shù)為范圍區(qū)間,如果為null,則默認(rèn)全部范圍;第二個(gè)參數(shù)為方向,為next/nextunique/prev/prevunique四個(gè),帶有unique的會(huì)去重
索引可以使用objectStore.createIndex("name", "name", {unique: true});來創(chuàng)建索引,分別為索引名,索引的屬性名,該屬性值是否唯一。調(diào)用objectStore.delete("name");來刪除索引。刪除索引不會(huì)影響數(shù)據(jù),所以沒有回調(diào)函數(shù)。
如果我們不想在主鍵上遍歷游標(biāo)或者獲取數(shù)據(jù),可以在數(shù)據(jù)集上取得新的索引列表:
// 直接切換索引 const index = objectStore("dimensions"); // 在該索引上進(jìn)行游標(biāo)遍歷 request = index.openCursor();
如果我們?cè)诜侵麈I的游標(biāo)中,想要去的主鍵值,調(diào)用index.getKey("007");
并發(fā)問題數(shù)據(jù)庫(kù)操作存在并發(fā)問題,但由于是異步,我們不用擔(dān)心,但是如果存在新版本變更還是會(huì)導(dǎo)致問題。所以打開數(shù)據(jù)庫(kù)時(shí)指定onversionchange()處理事件,可以避免這個(gè)問題,在setVesion()時(shí),如果觸發(fā)onerror()代表已經(jīng)打開了該數(shù)據(jù)庫(kù),無法現(xiàn)在更新版本,提示用戶關(guān)閉其它網(wǎng)頁(yè),重新調(diào)用更新。
總結(jié)如果需要與服務(wù)器實(shí)時(shí)交互,使用cookie,如果需要保存一些小信息字段,使用localStorage,如果只需要本次會(huì)話有效,使用sessionStorage,如果數(shù)據(jù)很大,使用indexedDB。使用什么技術(shù)跟業(yè)務(wù)場(chǎng)景匹配,但是技術(shù)還是都要了解都要會(huì),畢竟,巧婦難為無米之炊。
參考資料JavaScript高級(jí)程序設(shè)計(jì) 23章-離線應(yīng)用與客戶端存儲(chǔ)
storage事件使用:https://www.cnblogs.com/incon...
IDBCursor對(duì)象:https://developer.mozilla.org...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/92454.html
摘要:私有緩存就是用戶專享的,各級(jí)代理不能緩存的緩存。代表使用內(nèi)存中的緩存,則代表使用的是硬盤中的緩存,瀏覽器讀取緩存的順序?yàn)椤? 作為一名前端工作人員,前端的緩存知識(shí)是必須掌握的,因?yàn)橐粋€(gè)網(wǎng)站打開網(wǎng)頁(yè)的速度直接關(guān)系到用戶體驗(yàn),用戶粘度,而提高網(wǎng)頁(yè)的打開速度有很多方面需要優(yōu)化,其中比較重要的一點(diǎn)就是利用好緩存,緩存文件可以重復(fù)利用,還可以減少帶寬,降低網(wǎng)絡(luò)負(fù)荷。 1 緩存 緩存從宏觀上分為私有...
摘要:私有緩存就是用戶專享的,各級(jí)代理不能緩存的緩存。代表使用內(nèi)存中的緩存,則代表使用的是硬盤中的緩存,瀏覽器讀取緩存的順序?yàn)椤? 作為一名前端工作人員,前端的緩存知識(shí)是必須掌握的,因?yàn)橐粋€(gè)網(wǎng)站打開網(wǎng)頁(yè)的速度直接關(guān)系到用戶體驗(yàn),用戶粘度,而提高網(wǎng)頁(yè)的打開速度有很多方面需要優(yōu)化,其中比較重要的一點(diǎn)就是利用好緩存,緩存文件可以重復(fù)利用,還可以減少帶寬,降低網(wǎng)絡(luò)負(fù)荷。 1 緩存 緩存從宏觀上分為私有...
摘要:私有緩存就是用戶專享的,各級(jí)代理不能緩存的緩存。代表使用內(nèi)存中的緩存,則代表使用的是硬盤中的緩存,瀏覽器讀取緩存的順序?yàn)椤? 作為一名前端工作人員,前端的緩存知識(shí)是必須掌握的,因?yàn)橐粋€(gè)網(wǎng)站打開網(wǎng)頁(yè)的速度直接關(guān)系到用戶體驗(yàn),用戶粘度,而提高網(wǎng)頁(yè)的打開速度有很多方面需要優(yōu)化,其中比較重要的一點(diǎn)就是利用好緩存,緩存文件可以重復(fù)利用,還可以減少帶寬,降低網(wǎng)絡(luò)負(fù)荷。 1 緩存 緩存從宏觀上分為私有...
閱讀 972·2021-11-22 13:53
閱讀 2605·2021-10-15 09:40
閱讀 1090·2021-10-14 09:42
閱讀 3925·2021-09-22 15:59
閱讀 965·2021-09-02 09:47
閱讀 2541·2019-08-30 15:54
閱讀 1505·2019-08-29 17:14
閱讀 463·2019-08-29 15:15