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