Javascript LocalStorage,sessionStorage

2023-02-17 10:57 更新

Web 存儲(chǔ)對(duì)象 ?localStorage? 和 ?sessionStorage? 允許我們?cè)跒g覽器上保存鍵/值對(duì)。

它們有趣的是,在頁(yè)面刷新后(對(duì)于 sessionStorage)甚至瀏覽器完全重啟(對(duì)于 localStorage)后,數(shù)據(jù)仍然保留在瀏覽器中。我們很快就會(huì)看到。

我們已經(jīng)有了 cookie。為什么還要其他存儲(chǔ)對(duì)象呢?

  • 與 cookie 不同,Web 存儲(chǔ)對(duì)象不會(huì)隨每個(gè)請(qǐng)求被發(fā)送到服務(wù)器。因此,我們可以保存更多數(shù)據(jù)。大多數(shù)現(xiàn)代瀏覽器都允許保存至少 5MB 的數(shù)據(jù)(或更多),并且具有用于配置數(shù)據(jù)的設(shè)置。
  • 還有一點(diǎn)和 cookie 不同,服務(wù)器無(wú)法通過(guò) HTTP header 操縱存儲(chǔ)對(duì)象。一切都是在 JavaScript 中完成的。
  • 存儲(chǔ)綁定到源(域/協(xié)議/端口三者)。也就是說(shuō),不同協(xié)議或子域?qū)?yīng)不同的存儲(chǔ)對(duì)象,它們之間無(wú)法訪問(wèn)彼此數(shù)據(jù)。

兩個(gè)存儲(chǔ)對(duì)象都提供相同的方法和屬性:

  • ?setItem(key, value)? —— 存儲(chǔ)鍵/值對(duì)。
  • ?getItem(key)? —— 按照鍵獲取值。
  • ?removeItem(key)? —— 刪除鍵及其對(duì)應(yīng)的值。
  • ?clear()? —— 刪除所有數(shù)據(jù)。
  • ?key(index)? —— 獲取該索引下的鍵名。
  • ?length? —— 存儲(chǔ)的內(nèi)容的長(zhǎng)度。

正如你所看到的,它就像一個(gè) Map 集合(setItem/getItem/removeItem),但也允許通過(guò) key(index) 來(lái)按索引訪問(wèn)。

讓我們看看它是如何工作的吧。

localStorage 示例

localStorage 最主要的特點(diǎn)是:

  • 在同源的所有標(biāo)簽頁(yè)和窗口之間共享數(shù)據(jù)。
  • 數(shù)據(jù)不會(huì)過(guò)期。它在瀏覽器重啟甚至系統(tǒng)重啟后仍然存在。

例如,如果你運(yùn)行此代碼……

localStorage.setItem('test', 1);

……然后關(guān)閉/重新打開(kāi)瀏覽器,或者只是在不同的窗口打開(kāi)同一頁(yè)面,然后你可以這樣獲取它:

alert( localStorage.getItem('test') ); // 1

我們只需要在同一個(gè)源(域/端口/協(xié)議),URL 路徑可以不同。

在所有同源的窗口之間,localStorage 數(shù)據(jù)可以共享。因此,如果我們?cè)谝粋€(gè)窗口中設(shè)置了數(shù)據(jù),則在另一個(gè)窗口中也可以看到數(shù)據(jù)變化。

類(lèi)對(duì)象形式訪問(wèn)

我們還可以像使用一個(gè)普通對(duì)象那樣,讀取/設(shè)置鍵,像這樣:

// 設(shè)置 key
localStorage.test = 2;

// 獲取 key
alert( localStorage.test ); // 2

// 刪除 key
delete localStorage.test;

這是歷史原因造成的,并且大多數(shù)情況下都可行,但通常不建議這樣做,因?yàn)椋?

  1. 如果鍵是由用戶(hù)生成的,那么它可以是任何內(nèi)容,例如 length 或 toString,也可以是 localStorage 的另一種內(nèi)建方法。在這種情況下,getItem/setItem 可以正常工作,而類(lèi)對(duì)象訪問(wèn)的方式則會(huì)失敗:
  2. let key = 'length';
    localStorage[key] = 5; // Error,無(wú)法對(duì) length 進(jìn)行賦值
  3. 有一個(gè) ?storage? 事件,在我們更改數(shù)據(jù)時(shí)會(huì)觸發(fā)。但以類(lèi)對(duì)象方式訪問(wèn)時(shí),不會(huì)觸發(fā)該事件。我們將在本章的后面看到。

遍歷鍵

正如我們所看到的,這些方法提供了“按照鍵獲取/設(shè)置/刪除”的功能。但我們?nèi)绾潍@取所有保存的值或鍵呢?

不幸的是,存儲(chǔ)對(duì)象是不可迭代的。

一種方法是像遍歷數(shù)組那樣遍歷它們:

for(let i = 0; i < localStorage.length; i++) {
  let key = localStorage.key(i);
  alert(`${key}: ${localStorage.getItem(key)}`);
}

另一個(gè)方式是使用 for key in localStorage 循環(huán),就像處理常規(guī)對(duì)象一樣。

它會(huì)遍歷所有的鍵,但也會(huì)輸出一些我們不需要的內(nèi)建字段。

// 不好的嘗試
for(let key in localStorage) {
  alert(key); // 顯示 getItem,setItem 和其他內(nèi)建的東西
}

……因此,我們需要使用 hasOwnProperty 檢查來(lái)過(guò)濾掉原型中的字段:

for(let key in localStorage) {
  if (!localStorage.hasOwnProperty(key)) {
    continue; // 跳過(guò)像 "setItem","getItem" 等這樣的鍵
  }
  alert(`${key}: ${localStorage.getItem(key)}`);
}

……或者,使用 Object.keys 獲取只屬于“自己”的鍵,然后如果需要,可以遍歷它們:

let keys = Object.keys(localStorage);
for(let key of keys) {
  alert(`${key}: ${localStorage.getItem(key)}`);
}

后者有效,因?yàn)?nbsp;Object.keys 只返回屬于對(duì)象的鍵,會(huì)忽略原型上的。

僅字符串

請(qǐng)注意,鍵和值都必須是字符串。

如果是任何其他類(lèi)型,例數(shù)字或?qū)ο?,它?huì)被自動(dòng)轉(zhuǎn)換為字符串。

localStorage.user = {name: "John"};
alert(localStorage.user); // [object Object]

我們可以使用 JSON 來(lái)存儲(chǔ)對(duì)象:

localStorage.user = JSON.stringify({name: "John"});

// sometime later
let user = JSON.parse( localStorage.user );
alert( user.name ); // John

也可以對(duì)整個(gè)存儲(chǔ)對(duì)象進(jìn)行字符串化處理,例如出于調(diào)試目的:

// 為 JSON.stringify 增加了格式設(shè)置選項(xiàng),以使對(duì)象看起來(lái)更美觀
alert( JSON.stringify(localStorage, null, 2) );

sessionStorage

sessionStorage 對(duì)象的使用頻率比 localStorage 對(duì)象低得多。

屬性和方法是相同的,但是它有更多的限制:

  • ?sessionStorage? 的數(shù)據(jù)只存在于當(dāng)前瀏覽器標(biāo)簽頁(yè)。
    • 具有相同頁(yè)面的另一個(gè)標(biāo)簽頁(yè)中將會(huì)有不同的存儲(chǔ)。
    • 但是,它在同一標(biāo)簽頁(yè)下的 iframe 之間是共享的(假如它們來(lái)自相同的源)。
  • 數(shù)據(jù)在頁(yè)面刷新后仍然保留,但在關(guān)閉/重新打開(kāi)瀏覽器標(biāo)簽頁(yè)后不會(huì)被保留。

讓我們看看它的運(yùn)行效果。

運(yùn)行此代碼……

sessionStorage.setItem('test', 1);

……然后刷新頁(yè)面。這時(shí)你仍然可以獲取到數(shù)據(jù):

alert( sessionStorage.getItem('test') ); // after refresh: 1

……但是,如果你在另一個(gè)新的標(biāo)簽頁(yè)中打開(kāi)此頁(yè)面,然后在新頁(yè)面中再次運(yùn)行上面這行代碼,則會(huì)得到 null,表示“未找到數(shù)據(jù)”。

這是因?yàn)?nbsp;sessionStorage 不僅綁定到源,還綁定在同一瀏覽器標(biāo)簽頁(yè)。因此,sessionStorage 很少被使用。

Storage 事件

當(dāng) localStorage 或 sessionStorage 中的數(shù)據(jù)更新后,storage 事件就會(huì)觸發(fā),它具有以下屬性:

  • ?key? —— 發(fā)生更改的數(shù)據(jù)的 ?key?(如果調(diào)用的是 ?.clear()? 方法,則為 ?null?)。
  • ?oldValue? —— 舊值(如果是新增數(shù)據(jù),則為 ?null?)。
  • ?newValue? —— 新值(如果是刪除數(shù)據(jù),則為 ?null?)。
  • ?url? —— 發(fā)生數(shù)據(jù)更新的文檔的 url。
  • ?storageArea? —— 發(fā)生數(shù)據(jù)更新的 ?localStorage? 或 ?sessionStorage? 對(duì)象。

重要的是:該事件會(huì)在所有可訪問(wèn)到存儲(chǔ)對(duì)象的 window 對(duì)象上觸發(fā),導(dǎo)致當(dāng)前數(shù)據(jù)改變的 window 對(duì)象除外。

我們來(lái)詳細(xì)解釋一下。

想象一下,你有兩個(gè)窗口,它們具有相同的頁(yè)面。所以 localStorage 在它們之間是共享的。

你可以想在瀏覽器的兩個(gè)窗口中打開(kāi)此頁(yè)面來(lái)測(cè)試下面的代碼。

如果兩個(gè)窗口都在監(jiān)聽(tīng) window.onstorage 事件,那么每個(gè)窗口都會(huì)對(duì)另一個(gè)窗口中發(fā)生的更新作出反應(yīng)。

// 在其他文檔對(duì)同一存儲(chǔ)進(jìn)行更新時(shí)觸發(fā)
window.onstorage = event => { // 也可以使用 window.addEventListener('storage', event => {
  if (event.key != 'now') return;
  alert(event.key + ':' + event.newValue + " at " + event.url);
};

localStorage.setItem('now', Date.now());

請(qǐng)注意,該事件還包含:event.url —— 發(fā)生數(shù)據(jù)更新的文檔的 url。

并且,event.storageArea 包含存儲(chǔ)對(duì)象 —— sessionStorage 和 localStorage 具有相同的事件,所以 event.storageArea 引用了被修改的對(duì)象。我們可能會(huì)想設(shè)置一些東西,以“響應(yīng)”更改。

這允許同源的不同窗口交換消息。

現(xiàn)代瀏覽器還支持 Broadcast channel API,這是用于同源窗口之間通信的特殊 API,它的功能更全,但被支持的情況不好。有一些庫(kù)基于 localStorage 來(lái) polyfill 該 API,使其可以用在任何地方。

總結(jié)

Web 存儲(chǔ)對(duì)象 ?localStorage? 和 ?sessionStorage? 允許我們?cè)跒g覽器中保存鍵/值對(duì)。

  • ?key? 和 ?value? 都必須為字符串。
  • 存儲(chǔ)大小限制為 5MB+,具體取決于瀏覽器。
  • 它們不會(huì)過(guò)期。
  • 數(shù)據(jù)綁定到源(域/端口/協(xié)議)。
localStorage sessionStorage
在同源的所有標(biāo)簽頁(yè)和窗口之間共享數(shù)據(jù) 在當(dāng)前瀏覽器標(biāo)簽頁(yè)中可見(jiàn),包括同源的 iframe
瀏覽器重啟后數(shù)據(jù)仍然保留 頁(yè)面刷新后數(shù)據(jù)仍然保留(但標(biāo)簽頁(yè)關(guān)閉后數(shù)據(jù)則不再保留)

API:

  • ?setItem(key, value)? —— 存儲(chǔ)鍵/值對(duì)。
  • ?getItem(key)? —— 按照鍵獲取值。
  • ?removeItem(key)? —— 刪除鍵及其對(duì)應(yīng)的值。
  • ?clear()? —— 刪除所有數(shù)據(jù)。
  • ?key(index)? —— 獲取該索引下的鍵名。
  • ?length? —— 存儲(chǔ)的內(nèi)容的長(zhǎng)度。
  • 使用 ?Object.keys? 來(lái)獲取所有的鍵。
  • 我們將鍵作為對(duì)象屬性來(lái)訪問(wèn),在這種情況下,不會(huì)觸發(fā) ?storage? 事件。

Storage 事件:

  • 在調(diào)用 ?setItem?,?removeItem?,?clear? 方法后觸發(fā)。
  • 包含有關(guān)操作的所有數(shù)據(jù)(?key/oldValue/newValue?),文檔 ?url? 和存儲(chǔ)對(duì)象 ?storageArea?。
  • 在所有可訪問(wèn)到存儲(chǔ)對(duì)象的 ?window? 對(duì)象上觸發(fā),導(dǎo)致當(dāng)前數(shù)據(jù)改變的 ?window? 對(duì)象除外(對(duì)于 ?sessionStorage? 是在當(dāng)前標(biāo)簽頁(yè)下,對(duì)于 ?localStorage? 是在全局,即所有同源的窗口)。

任務(wù)


自動(dòng)保存表單字段

創(chuàng)建一個(gè) textarea 字段,每當(dāng)其值發(fā)生變化時(shí),可以將其“自動(dòng)保存”。

因此,如果用戶(hù)不小心關(guān)閉了頁(yè)面,然后重新打開(kāi),他會(huì)發(fā)現(xiàn)之前未完成的輸入仍然保留在那里。

打開(kāi)一個(gè)任務(wù)沙箱。


解決方案

使用沙箱打開(kāi)解決方案。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)