前端百題斬之我從瀏覽器控制臺(tái)看到了五種存儲(chǔ)方式
打開瀏覽器的開發(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è)保存在瀏覽器中的簡單的文本文件,該文件與特定的Web文檔關(guān)聯(lián)在一起,保存了該瀏覽器訪問這個(gè)Web文檔時(shí)的信息,當(dāng)瀏覽器再次訪問這個(gè)Web文檔時(shí)這些信息可供該文檔使用。(HTTP是無狀態(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è)流程分為兩類:一類是沒有Cookie信息狀態(tài)下的請(qǐng)求,另一類是存有Cookie狀態(tài)下的請(qǐng)求。
通過上面的流程圖可以看出,Cookie是在服務(wù)端生成的,經(jīng)過查詢資料了解到其是在從服務(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)過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ù)器上讀取,不能再通過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)!!!');
- });
通過請(qǐng)求 http://127/.0.0.1:8090 來看看其結(jié)果:
第一次返回的Cookie結(jié)果
后續(xù)請(qǐng)求所帶的Cookie信息
24.1.3 Cookie特點(diǎn)
- 每個(gè)Cookie不超過4096字節(jié);
- 每個(gè)域中Cookie個(gè)數(shù)有限制,就拿最新版來說:IE和Edge不超過50個(gè);Firefox不超過150個(gè);Opera不超過180個(gè);Safari和Chrome沒有限制;
- Cookie超過單個(gè)域的上限,瀏覽器會(huì)刪除之前設(shè)置的Cookie;
- 創(chuàng)建的Cookie超過最大限制,該Cookie會(huì)被靜默刪除;
- 可設(shè)置失效時(shí)間,沒有設(shè)置則會(huì)話結(jié)束會(huì)刪除Cookie;
- 每個(gè)請(qǐng)求均會(huì)攜帶Cookie,若Cookie過多會(huì)帶來性能問題;
- 受同源策略限制
24.1.4 Cookie的操作
Cookie存儲(chǔ)到瀏覽器端之后仍然可以對(duì)其進(jìn)行讀、寫、刪除,由于js對(duì)Cookie操作的支持并不是很友好,所以需要進(jì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的目的是解決通過客戶端存儲(chǔ)不需要頻繁發(fā)送回服務(wù)器的數(shù)據(jù)時(shí)使用cookie的問題,其提供了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)閉。(在該過程中刷新頁面其數(shù)據(jù)不受影響)
- 瀏覽器在實(shí)現(xiàn)存儲(chǔ)寫入時(shí)使用同步阻塞方式,數(shù)據(jù)會(huì)被立即提交到存儲(chǔ)。
- 獨(dú)立打開同一個(gè)窗口同一個(gè)頁面或一個(gè)Tab,sessionStorage也是不一樣的。
- 存儲(chǔ)空間大小限制為每個(gè)源不超過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è)源不超過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()方法清空localStorage
- localStorage.clear();
24.3 IndexedDB
24.3.1 IndexedDB整個(gè)結(jié)構(gòu)
對(duì)于整個(gè)IndexedDB為上述圖中所示:
一個(gè)域名下可以包含多個(gè)數(shù)據(jù)庫;
一個(gè)數(shù)據(jù)庫中包含多個(gè)對(duì)象倉庫,就類似與Mysql一個(gè)庫中有多張表一樣。
每個(gè)對(duì)象倉庫中包含多條數(shù)據(jù)記錄。
24.3.2 主要特點(diǎn)
IndexedDB是瀏覽器中存儲(chǔ)結(jié)構(gòu)化數(shù)據(jù)的一個(gè)方案,其設(shè)計(jì)幾乎是完全異步的,主要有以下特點(diǎn):
- 鍵值對(duì)存儲(chǔ) 在對(duì)象倉庫中,數(shù)據(jù)以“鍵值對(duì)”形式保存,每個(gè)數(shù)據(jù)記錄都有獨(dú)一無二的主鍵。
- 異步 IndexedDB操作時(shí)不會(huì)鎖死瀏覽器,用戶依然可以進(jìn)行其它操作。
- 支持事務(wù) 一些列操作步驟之中只要有一步失敗,整個(gè)事務(wù)就都取消,數(shù)據(jù)庫回滾到事務(wù)發(fā)生之前的狀態(tài),不存在只改寫一部分?jǐn)?shù)據(jù)的情況。
- 受同源策略限制 只能訪問自身域名下的數(shù)據(jù)庫,不能跨域訪問數(shù)據(jù)庫。
- 存儲(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ù)庫操作
IndexedDB像很多其它數(shù)據(jù)庫一樣有很多操作,下面就通過實(shí)戰(zhàn)的方式一起了解這些操作。
24.3.3.1 初始化數(shù)據(jù)庫
第一步是初始化數(shù)據(jù)庫,傳入創(chuàng)建的數(shù)據(jù)庫名和版本,獲取對(duì)應(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)錯(cuò)');
- reject('error');
- };
- this.request.onupgradeneeded = event =>{
- this.db = event.target.result;
- console.log('數(shù)據(jù)庫升級(jí)');
- resolve('upgradeneeded');
- };
- });
- }
- }
24.3.3.2 對(duì)象倉庫操作
數(shù)據(jù)是在對(duì)象倉庫中存儲(chǔ)的,創(chuàng)建好數(shù)據(jù)庫后則需要?jiǎng)?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)用代碼
上面寫了一個(gè)數(shù)據(jù)庫的類,但是仍然不知道怎么調(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ān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系執(zhí)鳶者公眾號(hào)。