前端百題斬之我從瀏覽器控制臺(tái)看到了五種存儲(chǔ)方式
打開(kāi)瀏覽器的開(kāi)發(fā)者工具中的Application部分,可以看到瀏覽器支持五種存儲(chǔ)方式:localStorage、sessionStorage、IndexedDB、WebSQL、Cookie。其中W3C 官方在 2011 年 11 月聲明已經(jīng)不再維護(hù) Web SQL Database 規(guī)范,所以本次主要討論另外的三類四種存儲(chǔ)方式:Cookie、Storage、IndexedDB。
24.1 Cookie
24.1.1 定義
Cookie是一個(gè)保存在瀏覽器中的簡(jiǎn)單的文本文件,該文件與特定的Web文檔關(guān)聯(lián)在一起,保存了該瀏覽器訪問(wèn)這個(gè)Web文檔時(shí)的信息,當(dāng)瀏覽器再次訪問(wèn)這個(gè)Web文檔時(shí)這些信息可供該文檔使用。(HTTP是無(wú)狀態(tài)的協(xié)議,即HTTP協(xié)議本身不對(duì)請(qǐng)求和響應(yīng)之間的通信狀態(tài)進(jìn)行保存,為了實(shí)現(xiàn)期望的保存狀態(tài)功能,引入了cookie技術(shù))
24.1.2 Cookie組成
在了解Cookie組成之前先了解一下Cookie的整個(gè)請(qǐng)求流程,這個(gè)流程分為兩類:一類是沒(méi)有Cookie信息狀態(tài)下的請(qǐng)求,另一類是存有Cookie狀態(tài)下的請(qǐng)求。
通過(guò)上面的流程圖可以看出,Cookie是在服務(wù)端生成的,經(jīng)過(guò)查詢資料了解到其是在從服務(wù)端發(fā)送的響應(yīng)報(bào)文內(nèi)的一個(gè)叫做Set-Cookie的首部字段信息,響應(yīng)報(bào)文中有該首部字段則通知客戶端保存Cookie,則Cookie的組成則跟Set-Cookie可以設(shè)置哪些值相關(guān),目前主要有以下幾類:
- NAME=VALUE Cookie的名稱和值,其中NAME是唯一標(biāo)識(shí)cookie的名稱,不區(qū)分大小寫;VALUE是存儲(chǔ)在Cookie里的字符串值,該值必須經(jīng)過(guò)URL編碼。
 - Domain=域名 Cookie有效的域,發(fā)送到這個(gè)域的所有請(qǐng)求都會(huì)包含對(duì)應(yīng)的Cookie。(若不指定則默認(rèn)為創(chuàng)建Cookie的服務(wù)器的域名)
 - Path=PATH 請(qǐng)求URL中包含這個(gè)路徑才會(huì)把Cookie發(fā)送到服務(wù)器(若不指定則默認(rèn)為文檔所在的文件目錄)
 - Expires=DATE Cookie的有效期,默認(rèn)情況下,瀏覽器會(huì)話結(jié)束后會(huì)刪除所有cookie。
 - Secure 設(shè)置后僅在HTTPS安全通信時(shí)才會(huì)發(fā)送Cookie
 - HttpOnly 設(shè)置后只能在服務(wù)器上讀取,不能再通過(guò)JavaScript讀取Cookie
 
- const express = require('express');
 - const app = express();
 - app.get('/', (req, res) => {
 - res.cookie('myCookie', 'myCookie', {
 - expires: new Date(Date.now() + 900000),
 - secure: true,
 - httpOnly: true
 - });
 - res.send('get請(qǐng)求已經(jīng)被處理');
 - })
 - app.listen(8090, () => {
 - console.log('8090端口已經(jīng)啟動(dòng)!?。?);
 - });
 
通過(guò)請(qǐng)求 http://127/.0.0.1:8090 來(lái)看看其結(jié)果:
第一次返回的Cookie結(jié)果
后續(xù)請(qǐng)求所帶的Cookie信息
24.1.3 Cookie特點(diǎn)
- 每個(gè)Cookie不超過(guò)4096字節(jié);
 - 每個(gè)域中Cookie個(gè)數(shù)有限制,就拿最新版來(lái)說(shuō):IE和Edge不超過(guò)50個(gè);Firefox不超過(guò)150個(gè);Opera不超過(guò)180個(gè);Safari和Chrome沒(méi)有限制;
 - Cookie超過(guò)單個(gè)域的上限,瀏覽器會(huì)刪除之前設(shè)置的Cookie;
 - 創(chuàng)建的Cookie超過(guò)最大限制,該Cookie會(huì)被靜默刪除;
 - 可設(shè)置失效時(shí)間,沒(méi)有設(shè)置則會(huì)話結(jié)束會(huì)刪除Cookie;
 - 每個(gè)請(qǐng)求均會(huì)攜帶Cookie,若Cookie過(guò)多會(huì)帶來(lái)性能問(wèn)題;
 - 受同源策略限制
 
24.1.4 Cookie的操作
Cookie存儲(chǔ)到瀏覽器端之后仍然可以對(duì)其進(jìn)行讀、寫、刪除,由于js對(duì)Cookie操作的支持并不是很友好,所以需要進(jìn)行一些簡(jiǎn)單的封裝。
- class CookieUtil {
 - // 獲取Cookie中的對(duì)應(yīng)屬性
 - static get(name) {
 - const cookies = document.cookie;
 - const cookiesArr = cookies.split(';');
 - for (let index = 0; index < cookiesArr.length; index++) {
 - const presentCookieArr = cookiesArr[index].split('=');
 - if (presentCookieArr[0] === name) {
 - return presentCookieArr[1];
 - }
 - }
 - return null;
 - }
 - // 設(shè)置對(duì)應(yīng)的Cookie值
 - static set(name, value, expires, path, domain, secure) {
 - let cookieText = `${name}=${value}`;
 - if (expires instanceof Date) {
 - cookieText += `; expire=${expires.toGMTString()}`;
 - }
 - if (path) {
 - cookieText += `; path=${path}`;
 - }
 - if (domain) {
 - cookieText += `; domain=${domain}`;
 - }
 - if (secure) {
 - cookieText += `; secure`;
 - }
 - document.cookie = cookieText;
 - }
 - // 刪除對(duì)應(yīng)的Cookie
 - static deleteCookie(name) {
 - CookieUtil.set(name, '', new Date(0));
 - }
 - }
 
24.2 Web Storage
Web Storage的目的是解決通過(guò)客戶端存儲(chǔ)不需要頻繁發(fā)送回服務(wù)器的數(shù)據(jù)時(shí)使用cookie的問(wèn)題,其提供了cookie之外的存儲(chǔ)會(huì)話數(shù)據(jù)的途徑和跨會(huì)話持久化存儲(chǔ)大量數(shù)據(jù)的機(jī)制,其主要有兩個(gè)對(duì)象:localStorage和sessionStorage,localStorage是永久存儲(chǔ)機(jī)制,sessionStorage是跨會(huì)話的存儲(chǔ)機(jī)制。
24.2.1 sessionStorage
sessionStorage是跨會(huì)話的存儲(chǔ)機(jī)制,具有以下特點(diǎn):
- sessionStorage對(duì)象值存儲(chǔ)會(huì)話數(shù)據(jù),其生命周期會(huì)存儲(chǔ)到瀏覽器關(guān)閉。(在該過(guò)程中刷新頁(yè)面其數(shù)據(jù)不受影響)
 - 瀏覽器在實(shí)現(xiàn)存儲(chǔ)寫入時(shí)使用同步阻塞方式,數(shù)據(jù)會(huì)被立即提交到存儲(chǔ)。
 - 獨(dú)立打開(kāi)同一個(gè)窗口同一個(gè)頁(yè)面或一個(gè)Tab,sessionStorage也是不一樣的。
 - 存儲(chǔ)空間大小限制為每個(gè)源不超過(guò)5M。
 
- // 使用方法存儲(chǔ)數(shù)據(jù)
 - sessionStorage.setItem('sessionName1', 'value1');
 - // 使用屬性存儲(chǔ)數(shù)據(jù)
 - sessionStorage.sessionName2 = 'value2';
 - // 使用方法取得數(shù)據(jù)
 - const sessionValue1 = sessionStorage.getItem('sessionName1');
 - console.log('sessionValue1的值為:', sessionValue1);
 - // 使用屬性取得數(shù)據(jù)
 - const sessionValue2 = sessionStorage.sessionName2;
 - console.log('sessionValue2的值為:', sessionValue2);
 - // 循環(huán)遍歷sessionStarage
 - for (let index = 0; index < sessionStorage.length; index++) {
 - // 使用key()方法獲得指定索引處的名稱
 - const key = sessionStorage.key(index);
 - const value = sessionStorage.getItem(key);
 - console.log('循環(huán)遍歷結(jié)果:', key, value);
 - }
 - // 使用方法刪除值
 - sessionStorage.removeItem('sessionName1');
 - // 使用delete刪除值
 - delete sessionStorage.sessionName2;
 - // 使用clear()方法清空sessionStorage
 - sessionStorage.clear();
 
24.2.2 localStorage
localStorage是永久存儲(chǔ)機(jī)制,具有以下特點(diǎn):
- 生命周期是永久的,除非被清除,否則永久保存。
 - 存儲(chǔ)空間大小限制為每個(gè)源不超過(guò)5M。
 - 受同源策略限制。
 - 瀏覽器存儲(chǔ)時(shí)采用同步存儲(chǔ)方式。
 
- // 使用方法存儲(chǔ)數(shù)據(jù)
 - localStorage.setItem('localName1', 'value1');
 - // 使用屬性存儲(chǔ)數(shù)據(jù)
 - localStorage.localName2 = 'value2';
 - // 使用方法取得數(shù)據(jù)
 - const localValue1 = localStorage.getItem('localName1');
 - console.log('localValue1的值為:', localValue1);
 - // 使用屬性取得數(shù)據(jù)
 - const localValue2 = localStorage.localName2;
 - console.log('localValue2的值為:', localValue2);
 - // 循環(huán)遍歷localStarage
 - for (let index = 0; index < localStorage.length; index++) {
 - // 使用key()方法獲得指定索引處的名稱
 - const key = localStorage.key(index);
 - const value = localStorage.getItem(key);
 - console.log('循環(huán)遍歷結(jié)果:', key, value);
 - }
 - // 使用方法刪除值
 - localStorage.removeItem('localName1');
 - // 使用delete刪除值
 - delete localStorage.localName2;
 - // 使用clear()方法清空l(shuí)ocalStorage
 - localStorage.clear();
 
24.3 IndexedDB
24.3.1 IndexedDB整個(gè)結(jié)構(gòu)
對(duì)于整個(gè)IndexedDB為上述圖中所示:
一個(gè)域名下可以包含多個(gè)數(shù)據(jù)庫(kù);
一個(gè)數(shù)據(jù)庫(kù)中包含多個(gè)對(duì)象倉(cāng)庫(kù),就類似與Mysql一個(gè)庫(kù)中有多張表一樣。
每個(gè)對(duì)象倉(cāng)庫(kù)中包含多條數(shù)據(jù)記錄。
24.3.2 主要特點(diǎn)
IndexedDB是瀏覽器中存儲(chǔ)結(jié)構(gòu)化數(shù)據(jù)的一個(gè)方案,其設(shè)計(jì)幾乎是完全異步的,主要有以下特點(diǎn):
- 鍵值對(duì)存儲(chǔ) 在對(duì)象倉(cāng)庫(kù)中,數(shù)據(jù)以“鍵值對(duì)”形式保存,每個(gè)數(shù)據(jù)記錄都有獨(dú)一無(wú)二的主鍵。
 - 異步 IndexedDB操作時(shí)不會(huì)鎖死瀏覽器,用戶依然可以進(jìn)行其它操作。
 - 支持事務(wù) 一些列操作步驟之中只要有一步失敗,整個(gè)事務(wù)就都取消,數(shù)據(jù)庫(kù)回滾到事務(wù)發(fā)生之前的狀態(tài),不存在只改寫一部分?jǐn)?shù)據(jù)的情況。
 - 受同源策略限制 只能訪問(wèn)自身域名下的數(shù)據(jù)庫(kù),不能跨域訪問(wèn)數(shù)據(jù)庫(kù)。
 - 存儲(chǔ)空間大 每個(gè)源都有存儲(chǔ)空間的限制,而且這個(gè)限制跟瀏覽器有關(guān),例如Firefox限制每個(gè)源50MB,Chrome為5MB。
 - 支持二進(jìn)制存儲(chǔ) 不僅可以存儲(chǔ)字符串,還可以存儲(chǔ)二進(jìn)制數(shù)據(jù)(ArrayBuffer和Blob)
 
24.3.3 數(shù)據(jù)庫(kù)操作
IndexedDB像很多其它數(shù)據(jù)庫(kù)一樣有很多操作,下面就通過(guò)實(shí)戰(zhàn)的方式一起了解這些操作。
24.3.3.1 初始化數(shù)據(jù)庫(kù)
第一步是初始化數(shù)據(jù)庫(kù),傳入創(chuàng)建的數(shù)據(jù)庫(kù)名和版本,獲取對(duì)應(yīng)的數(shù)據(jù)庫(kù)操作實(shí)例。
- class IndexedDBOperation {
 - constructor(databaseName, version) {
 - this.atabaseName = databaseName;
 - this.version = version;
 - this.request = null;
 - this.db = null;
 - }
 - // 數(shù)據(jù)庫(kù)初始化操作
 - init() {
 - this.request = window.indexedDB.open(this.databaseName, this.version);
 - return new Promise((resolve, reject) => {
 - this.request.onsuccess = event => {
 - this.db = event.target.result;
 - console.log('數(shù)據(jù)庫(kù)打開(kāi)成功');
 - resolve('success');
 - };
 - this.request.onerror = event => {
 - console.log('數(shù)據(jù)庫(kù)打開(kāi)報(bào)錯(cuò)');
 - reject('error');
 - };
 - this.request.onupgradeneeded = event =>{
 - this.db = event.target.result;
 - console.log('數(shù)據(jù)庫(kù)升級(jí)');
 - resolve('upgradeneeded');
 - };
 - });
 - }
 - }
 
24.3.3.2 對(duì)象倉(cāng)庫(kù)操作
數(shù)據(jù)是在對(duì)象倉(cāng)庫(kù)中存儲(chǔ)的,創(chuàng)建好數(shù)據(jù)庫(kù)后則需要?jiǎng)?chuàng)建所需的數(shù)據(jù)倉(cāng)庫(kù)
- class IndexedDBOperation {
 - // ……
 - // 創(chuàng)建數(shù)據(jù)倉(cāng)庫(kù)
 - createObjectStore(objectStoreName, options) {
 - let objectStore = null;
 - if (!this.db.objectStoreNames.contains(objectStoreName)) {
 - objectStore = this.db.createObjectStore(objectStoreName, options);
 - }
 - return objectStore;
 - }
 - }
 
24.3.3.3 數(shù)據(jù)操作
不管是關(guān)系型數(shù)據(jù)庫(kù)還是非關(guān)系型數(shù)據(jù)庫(kù),CURD肯定是必不可少的,誰(shuí)讓我們是“CURD工程師”呢!!!
- class IndexedDBOperation {
 - // ……
 - // 新增內(nèi)容
 - add(objectStore, content) {
 - objectStore.add(content);
 - }
 - // 獲取內(nèi)容
 - get(objectStore, id) {
 - const request = objectStore.get(id);
 - return new Promise((resolve, reject) => {
 - request.onsuccess = resolve;
 - request.onerror = reject;
 - });
 - }
 - // 更新內(nèi)容
 - update(objectStore, content) {
 - const request = objectStore.put(content);
 - request.onsuccess = event => {
 - console.log('更新成功');
 - };
 - request.onerror = event => {
 - console.log('更新失敗');
 - };
 - }
 - // 刪除內(nèi)容
 - remove(objectStore, deleteId) {
 - const request = objectStore.delete(deleteId);
 - request.onsuccess = event => {
 - console.log('刪除成功');
 - };
 - request.onerror = event => {
 - console.log('刪除失敗');
 - };
 - }
 - }
 
24.3.3.5 調(diào)用代碼
上面寫了一個(gè)數(shù)據(jù)庫(kù)的類,但是仍然不知道怎么調(diào)用呀,下面就用一個(gè)demo講述其調(diào)用。
- class IndexedDBOperation {
 - // ……
 - // 打印全部數(shù)據(jù)
 - printAllDataByCursor(objectStore) {
 - const cursorRequest = objectStore.openCursor();
 - cursorRequest.onsuccess = event => {
 - const cursor = event.target.result;
 - if (cursor) {
 - console.log(`利用游標(biāo)打印的內(nèi)容,id為${cursor.key}, 值為${cursor.value}`);
 - // 移動(dòng)到下一條記錄
 - cursor.continue();
 - }
 - };
 - }
 - }
 
本文轉(zhuǎn)載自微信公眾號(hào)「執(zhí)鳶者」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系執(zhí)鳶者公眾號(hào)。





















 
 
 





 
 
 
 