前端百題斬之我從瀏覽器控制臺看到了五種存儲方式
打開瀏覽器的開發(fā)者工具中的Application部分,可以看到瀏覽器支持五種存儲方式:localStorage、sessionStorage、IndexedDB、WebSQL、Cookie。其中W3C 官方在 2011 年 11 月聲明已經(jīng)不再維護 Web SQL Database 規(guī)范,所以本次主要討論另外的三類四種存儲方式:Cookie、Storage、IndexedDB。
24.1 Cookie
24.1.1 定義
Cookie是一個保存在瀏覽器中的簡單的文本文件,該文件與特定的Web文檔關(guān)聯(lián)在一起,保存了該瀏覽器訪問這個Web文檔時的信息,當瀏覽器再次訪問這個Web文檔時這些信息可供該文檔使用。(HTTP是無狀態(tài)的協(xié)議,即HTTP協(xié)議本身不對請求和響應(yīng)之間的通信狀態(tài)進行保存,為了實現(xiàn)期望的保存狀態(tài)功能,引入了cookie技術(shù))
24.1.2 Cookie組成
在了解Cookie組成之前先了解一下Cookie的整個請求流程,這個流程分為兩類:一類是沒有Cookie信息狀態(tài)下的請求,另一類是存有Cookie狀態(tài)下的請求。
通過上面的流程圖可以看出,Cookie是在服務(wù)端生成的,經(jīng)過查詢資料了解到其是在從服務(wù)端發(fā)送的響應(yīng)報文內(nèi)的一個叫做Set-Cookie的首部字段信息,響應(yīng)報文中有該首部字段則通知客戶端保存Cookie,則Cookie的組成則跟Set-Cookie可以設(shè)置哪些值相關(guān),目前主要有以下幾類:
- NAME=VALUE Cookie的名稱和值,其中NAME是唯一標識cookie的名稱,不區(qū)分大小寫;VALUE是存儲在Cookie里的字符串值,該值必須經(jīng)過URL編碼。
- Domain=域名 Cookie有效的域,發(fā)送到這個域的所有請求都會包含對應(yīng)的Cookie。(若不指定則默認為創(chuàng)建Cookie的服務(wù)器的域名)
- Path=PATH 請求URL中包含這個路徑才會把Cookie發(fā)送到服務(wù)器(若不指定則默認為文檔所在的文件目錄)
- Expires=DATE Cookie的有效期,默認情況下,瀏覽器會話結(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é)果
后續(xù)請求所帶的Cookie信息
24.1.3 Cookie特點
- 每個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過多會帶來性能問題;
- 受同源策略限制
24.1.4 Cookie的操作
Cookie存儲到瀏覽器端之后仍然可以對其進行讀、寫、刪除,由于js對Cookie操作的支持并不是很友好,所以需要進行一些簡單的封裝。
- 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));
- }
- }
24.2 Web Storage
Web Storage的目的是解決通過客戶端存儲不需要頻繁發(fā)送回服務(wù)器的數(shù)據(jù)時使用cookie的問題,其提供了cookie之外的存儲會話數(shù)據(jù)的途徑和跨會話持久化存儲大量數(shù)據(jù)的機制,其主要有兩個對象:localStorage和sessionStorage,localStorage是永久存儲機制,sessionStorage是跨會話的存儲機制。
24.2.1 sessionStorage
sessionStorage是跨會話的存儲機制,具有以下特點:
- sessionStorage對象值存儲會話數(shù)據(jù),其生命周期會存儲到瀏覽器關(guān)閉。(在該過程中刷新頁面其數(shù)據(jù)不受影響)
- 瀏覽器在實現(xiàn)存儲寫入時使用同步阻塞方式,數(shù)據(jù)會被立即提交到存儲。
- 獨立打開同一個窗口同一個頁面或一個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();
24.2.2 localStorage
localStorage是永久存儲機制,具有以下特點:
- 生命周期是永久的,除非被清除,否則永久保存。
- 存儲空間大小限制為每個源不超過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();
24.3 IndexedDB
24.3.1 IndexedDB整個結(jié)構(gòu)
對于整個IndexedDB為上述圖中所示:
一個域名下可以包含多個數(shù)據(jù)庫;
一個數(shù)據(jù)庫中包含多個對象倉庫,就類似與Mysql一個庫中有多張表一樣。
每個對象倉庫中包含多條數(shù)據(jù)記錄。
24.3.2 主要特點
IndexedDB是瀏覽器中存儲結(jié)構(gòu)化數(shù)據(jù)的一個方案,其設(shè)計幾乎是完全異步的,主要有以下特點:
- 鍵值對存儲 在對象倉庫中,數(shù)據(jù)以“鍵值對”形式保存,每個數(shù)據(jù)記錄都有獨一無二的主鍵。
- 異步 IndexedDB操作時不會鎖死瀏覽器,用戶依然可以進行其它操作。
- 支持事務(wù) 一些列操作步驟之中只要有一步失敗,整個事務(wù)就都取消,數(shù)據(jù)庫回滾到事務(wù)發(fā)生之前的狀態(tài),不存在只改寫一部分數(shù)據(jù)的情況。
- 受同源策略限制 只能訪問自身域名下的數(shù)據(jù)庫,不能跨域訪問數(shù)據(jù)庫。
- 存儲空間大 每個源都有存儲空間的限制,而且這個限制跟瀏覽器有關(guān),例如Firefox限制每個源50MB,Chrome為5MB。
- 支持二進制存儲 不僅可以存儲字符串,還可以存儲二進制數(shù)據(jù)(ArrayBuffer和Blob)
24.3.3 數(shù)據(jù)庫操作
IndexedDB像很多其它數(shù)據(jù)庫一樣有很多操作,下面就通過實戰(zhàn)的方式一起了解這些操作。
24.3.3.1 初始化數(shù)據(jù)庫
第一步是初始化數(shù)據(jù)庫,傳入創(chuàng)建的數(shù)據(jù)庫名和版本,獲取對應(yīng)的數(shù)據(jù)庫操作實例。
- 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ù)庫打開報錯');
- reject('error');
- };
- this.request.onupgradeneeded = event =>{
- this.db = event.target.result;
- console.log('數(shù)據(jù)庫升級');
- resolve('upgradeneeded');
- };
- });
- }
- }
24.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;
- }
- }
24.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('刪除失敗');
- };
- }
- }
24.3.3.5 調(diào)用代碼
上面寫了一個數(shù)據(jù)庫的類,但是仍然不知道怎么調(diào)用呀,下面就用一個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(`利用游標打印的內(nèi)容,id為${cursor.key}, 值為${cursor.value}`);
- // 移動到下一條記錄
- cursor.continue();
- }
- };
- }
- }
本文轉(zhuǎn)載自微信公眾號「執(zhí)鳶者」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系執(zhí)鳶者公眾號。