一文搞懂Cookie、Storage、IndexedDB
隨著前端技術的發展,存儲變的越來越重要,就目前來看,瀏覽器主要支持三類存儲:Cookie、Storage、IndexedDB,下面分別介紹這三類存儲方式。
一、Cookie
1.1 定義
Cookie是一個保存在瀏覽器中的簡單的文本文件,該文件與特定的Web文檔關聯在一起,保存了該瀏覽器訪問這個Web文檔時的信息,當瀏覽器再次訪問這個Web文檔時這些信息可供該文檔使用。(HTTP是無狀態的協議,即HTTP協議本身不對請求和響應之間的通信狀態進行保存,為了實現期望的保存狀態功能,引入了cookie技術)
1.2 Cookie組成
在了解Cookie組成之前先了解一下Cookie的整個請求流程,這個流程分為類:一類是沒有Cookie信息狀態下的請求,另一類是存有Cookie狀態下的請求。
通過上面的流程圖可以看出,Cookie是在服務端生成的,經過查詢資料了解到其是在從服務端發送的響應報文內的一個叫做Set-Cookie的首部字段信息,響應報文中有該首部字段則通知客戶端保存Cookie,則Cookie的組成則跟Set-Cookie可以設置哪些值相關,目前主要有以下幾類:
NAME=VALUE
Cookie的名稱和值,其中NAME是唯一標識cookie的名稱,不區分大小寫;VALUE是存儲在Cookie里的字符串值,該值必須經過URL編碼。
Domain=域名
Cookie有效的域,發送到這個域的所有請求都會包含對應的Cookie。(若不指定則默認為創建Cookie的服務器的域名)
Path=PATH
請求URL中包含這個路徑才會把Cookie發送到服務器(若不指定則默認為文檔所在的文件目錄)
Expires=DATE
Cookie的有效期,默認情況下,瀏覽器會話結束后會刪除所有cookie。
Secure
設置后僅在HTTPS安全通信時才會發送Cookie
HttpOnly
設置后只能在服務器上讀取,不能再通過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請求已經被處理');
- })
- app.listen(8090, () => {
- console.log('8090端口已經啟動!!!');
- });
通過請求 http://127/.0.0.1:8090 來看看其結果:
第一次返回的Cookie結果
2. 后續請求所帶的Cookie信息
1.3 Cookie特點
- 每個Cookie不超過4096字節;
- 每個域中Cookie個數有限制,就拿最新版來說:IE和Edge不超過50個;Firefox不超過150個;Opera不超過180個;Safari和Chrome沒有限制;
- Cookie超過單個域的上限,瀏覽器會刪除之前設置的Cookie;
- 創建的Cookie超過最大限制,該Cookie會被靜默刪除;
- 可設置失效時間,沒有設置則會話結束會刪除Cookie;
- 每個請求均會攜帶Cookie,若Cookie過來會帶來性能問題;
- 受同源策略限制
1.4 Cookie的操作
Cookie存儲到瀏覽器端之后仍然可以對其進行讀、寫、刪除,由于js對Cookie操作的支持并不是很友好,所以需要進行一些簡單的封裝。
- class CookieUtil {
- // 獲取Cookie中的對應屬性
- 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;
- }
- // 設置對應的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;
- }
- // 刪除對應的Cookie
- static deleteCookie(name) {
- CookieUtil.set(name, '', new Date(0));
- }
- }
二、Web Storage
Web Storage的目的是解決通過客戶端存儲不需要頻繁發送回服務器的數據時使用cookie的問題,其提供了cookie之外的存儲會話數據的途徑和跨會話持久化存儲大量數據的機制,其主要有兩個對象:localStorage和sessionStorage,localStorage是永久存儲機制,sessionStorage是跨會話的存儲機制。
2.1 sessionStorage
sessionStorage是跨會話的存儲機制,具有以下特點:
- sessionStorage對象值存儲會話數據,其生命周期會存儲到瀏覽器關閉。(在該過程中刷新頁面其數據不受影響)
- 瀏覽器在實現存儲寫入時使用同步阻塞方式,數據會被立即提交到存儲。
- 獨立打開同一個窗口同一個頁面或一個Tab,sessionStorage也是不一樣的。
- 存儲空間大小限制為每個源不超過5M。
- // 使用方法存儲數據
- sessionStorage.setItem('sessionName1', 'value1');
- // 使用屬性存儲數據
- sessionStorage.sessionName2 = 'value2';
- // 使用方法取得數據
- const sessionValue1 = sessionStorage.getItem('sessionName1');
- console.log('sessionValue1的值為:', sessionValue1);
- // 使用屬性取得數據
- const sessionValue2 = sessionStorage.sessionName2;
- console.log('sessionValue2的值為:', sessionValue2);
- // 循環遍歷sessionStarage
- for (let index = 0; index < sessionStorage.length; index++) {
- // 使用key()方法獲得指定索引處的名稱
- const key = sessionStorage.key(index);
- const value = sessionStorage.getItem(key);
- console.log('循環遍歷結果:', key, value);
- }
- // 使用方法刪除值
- sessionStorage.removeItem('sessionName1');
- // 使用delete刪除值
- delete sessionStorage.sessionName2;
- // 使用clear()方法清空sessionStorage
- sessionStorage.clear();
2.2 localStorage
localStorage是永久存儲機制,具有以下特點:
- 生命周期是永久的,除非被清除,否則永久保存。
- 存儲空間大小限制為每個源不超過5M。
- 受同源策略限制。
- 瀏覽器存儲時采用同步存儲方式。
- // 使用方法存儲數據
- localStorage.setItem('localName1', 'value1');
- // 使用屬性存儲數據
- localStorage.localName2 = 'value2';
- // 使用方法取得數據
- const localValue1 = localStorage.getItem('localName1');
- console.log('localValue1的值為:', localValue1);
- // 使用屬性取得數據
- const localValue2 = localStorage.localName2;
- console.log('localValue2的值為:', localValue2);
- // 循環遍歷localStarage
- for (let index = 0; index < localStorage.length; index++) {
- // 使用key()方法獲得指定索引處的名稱
- const key = localStorage.key(index);
- const value = localStorage.getItem(key);
- console.log('循環遍歷結果:', key, value);
- }
- // 使用方法刪除值
- localStorage.removeItem('localName1');
- // 使用delete刪除值
- delete localStorage.localName2;
- // 使用clear()方法清空localStorage
- localStorage.clear();
三、IndexedDB
3.1 IndexedDB整個結構
對于整個IndexedDB為上述圖中所示:
- 一個域名下可以包含多個數據庫;
- 一個數據庫中包含多個對象倉庫,就類似于Mysql一個庫中有多張表一樣。
- 每個對象倉庫中包含多條數據記錄。
3.2 主要特點
IndexedDB是瀏覽器中存儲結構化數據的一個方案,其設計幾乎是完全異步的,主要有以下特點:
鍵值對存儲
在對象倉庫中,數據以“鍵值對”形式保存,每個數據記錄都有獨一無二的主鍵。
異步
IndexedDB操作時不會鎖死瀏覽器,用戶依然可以進行其它操作。
支持事務
一些列操作步驟之中只要有一步失敗,整個事務就都取消,數據庫回滾到事務發生之前的狀態,不存在只改寫一部分數據的情況。
受同源策略限制
只能訪問自身域名下的數據庫,不能跨域訪問數據庫。
存儲空間大
每個源都有存儲空間的限制,而且這個限制跟瀏覽器有關,例如Firefox限制每個源50MB,Chrome為5MB。
支持二進制存儲
不僅可以存儲字符串,還可以存儲二進制數據(ArrayBuffer和Blob)
3.3 數據庫操作
IndexedDB像很多其它數據庫一樣有很多操作,下面就通過實戰的方式一起了解這些操作。
3.3.1 初始化數據庫
第一步是初始化數據庫,傳入創建的數據庫名和版本,獲取對應的數據庫操作實例。
- class IndexedDBOperation {
- constructor(databaseName, version) {
- this.atabaseName = databaseName;
- this.version = version;
- this.request = null;
- this.db = null;
- }
- // 數據庫初始化操作
- 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('數據庫打開成功');
- resolve('success');
- };
- this.request.onerror = event => {
- console.log('數據庫打開報錯');
- reject('error');
- };
- this.request.onupgradeneeded = event =>{
- this.db = event.target.result;
- console.log('數據庫升級');
- resolve('upgradeneeded');
- };
- });
- }
- }
3.3.2 對象倉庫操作
數據是在對象倉庫中存儲的,創建好數據庫后則需要創建所需的數據倉庫
- class IndexedDBOperation {
- // ……
- // 創建數據倉庫
- createObjectStore(objectStoreName, options) {
- let objectStore = null;
- if (!this.db.objectStoreNames.contains(objectStoreName)) {
- objectStore = this.db.createObjectStore(objectStoreName, options);
- }
- return objectStore;
- }
- }
3.3.3 數據操作
不管是關系型數據庫還是非關系型數據庫,CURD肯定是必不可少的,誰讓我們是“CURD工程師”呢!!!
- class IndexedDBOperation {
- // ……
- // 新增內容
- add(objectStore, content) {
- objectStore.add(content);
- }
- // 獲取內容
- get(objectStore, id) {
- const request = objectStore.get(id);
- return new Promise((resolve, reject) => {
- request.onsuccess = resolve;
- request.onerror = reject;
- });
- }
- // 更新內容
- update(objectStore, content) {
- const request = objectStore.put(content);
- request.onsuccess = event => {
- console.log('更新成功');
- };
- request.onerror = event => {
- console.log('更新失敗');
- };
- }
- // 刪除內容
- remove(objectStore, deleteId) {
- const request = objectStore.delete(deleteId);
- request.onsuccess = event => {
- console.log('刪除成功');
- };
- request.onerror = event => {
- console.log('刪除失敗');
- };
- }
- }
3.3.4 遍歷內容
提到IndexedDB數據庫,不得不提其的游標操作。
- class IndexedDBOperation {
- // ……
- // 打印全部數據
- printAllDataByCursor(objectStore) {
- const cursorRequest = objectStore.openCursor();
- cursorRequest.onsuccess = event => {
- const cursor = event.target.result;
- if (cursor) {
- console.log(`利用游標打印的內容,id為${cursor.key}, 值為${cursor.value}`);
- // 移動到下一條記錄
- cursor.continue();
- }
- };
- }
- }
3.3.5 調用代碼
上面寫了一個數據庫的類,但是仍然不知道怎么調用呀,下面就用一個demo講述其調用。
- const indexedDBOperation = new IndexedDBOperation('dbName1', 1);
- indexedDBOperation
- .init()
- .then(type => {
- const objectStoreName = 'testObjectStore';
- if (type === 'upgradeneeded') {
- indexedDBOperation.createObjectStore(objectStoreName, {
- keyPath: 'id'
- });
- }
- const transaction = indexedDBOperation.db.transaction([objectStoreName], 'readwrite');
- const objectStore = transaction.objectStore(objectStoreName);
- indexedDBOperation
- .get(objectStore, 1)
- .then(event => {
- if (event.target.result) {
- indexedDBOperation.update(objectStore, {
- id: 1,
- name: '執鳶者+紙鳶'
- });
- console.log('數據庫中已經存在', event.target.result, ',則進行更新操作');
- }
- else {
- indexedDBOperation.add(objectStore, {
- id: 1,
- name: '執鳶者'
- });
- console.log('數據庫中不存在,則進行添加');
- }
- })
- .catch(console.log);
- indexedDBOperation.printAllDataByCursor(objectStore);
- transaction.onsuccess = event => {
- console.log('事務操作成功');
- };
- transaction.onerror = event => {
- console.log('事務操作失敗');
- };
- transaction.oncomplete = event => {
- console.log('整個事務成功完成');
- }
- })
- .catch(console.log);
參考文獻
- 瀏覽器數據庫 IndexedDB 入門教程
- javascript高級程序設計4
- IndexedDB API
本文轉載自微信公眾號「執鳶者」,可以通過以下二維碼關注。轉載本文請聯系執鳶者公眾號。