Web端IM聊天訊息該不該用瀏覽器本地儲存?一文即懂!

JackJiang發表於2024-11-29
本文由轉轉技術團隊劉筱雨分享,原題“一文讀懂瀏覽器本地儲存:Web Storage”,下文進行了排版和內容最佳化。

1、引言

鑑於目前瀏覽器技術的進步(主要是HTML5的普及),在Web網頁端IM聊天應用的技術選型階段,很多開發者都會糾結到底該不該像原生移動端IM那樣將聊天記錄快取在瀏覽器的本地,還是像傳統Web端即時通訊那樣繼續儲存在服務端?本文將為你簡潔明瞭地講清楚瀏覽器本地儲存技術(Web Storage),然後你就知道到底該怎麼選擇了。瀏覽器本地儲存是指瀏覽器提供的一種機制,允許 Web 應用程式在瀏覽器端儲存資料,以便在使用者下次訪問時可以快速獲取和使用這些資料。一共兩種儲存方式:localStorage 和 sessionStorage,本文將主要圍繞這兩種技術來進行總結。
圖片
技術交流:

  • 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》
  • 開源IM框架原始碼:https://github.com/JackJiang2011/MobileIMSDK
    (備用地址點此)(本文已同步釋出於:http://www.52im.net/thread-4745-1-1.html

2、初識瀏覽器本地儲存(localStorage和sessionStorage)

2.1區別

localStorage 和 sessionStorage 的主要區別是生命週期。具體區別如下:
圖片
容量限制的目的是防止濫用本地儲存空間,導致使用者瀏覽器變慢。

2.2瀏覽器相容性

1)現在的瀏覽器基本上都是支援這兩種 Storage 特性的。各瀏覽器支援版本如下:
圖片
2)如果使用的是老式瀏覽器,比如Internet Explorer 6、7 或者其他,就需要在使用前檢測瀏覽器是否支援本地儲存或者是否被停用。
以 localStorage 為例:

if(window.localStorage){

alert("瀏覽器支援 localStorage");

} else {

alert("瀏覽器不支援 localStorage");

}

3)某些瀏覽器版本使用過程中,會出現 Storage 不能正常使用的情況,記得新增 try/catch。

以 localStorage 為例:
if(window.localStorage){

try {

localStorage.setItem("username", "name");

alert("瀏覽器支援 localStorage");

} catch (e) {

alert("瀏覽器支援 localStorage 後不可使用");

}

} else {

alert("瀏覽器不支援 localStorage");

}

3、基本用法演示

3.1 基本API

localStorage 和 sessionStorage 提供了相同的方法進行儲存、檢索和刪除。

常用的方法如下:

1)設定資料:setItem(key, value)。儲存的值可以是字串、數字、布林、陣列和物件。物件和陣列必須轉換為 string 進行儲存。JSON.parse() 和 JSON.stringify() 方法可以將陣列、物件等值型別轉換為字串型別,從而儲存到 Storage 中(示例程式碼如下)。

localStorage.setItem("username", "name"); // "name"

localStorage.setItem("count", 1); // "1"

localStorage.setItem("isOnline", true); // "true"

sessionStorage.setItem("username", "name");

// user 儲存時,先使用 JSON 序列化,否則儲存的是[object Object]

const user = { "username": "name" };

localStorage.setItem("user", JSON.stringify(user));

sessionStorage.setItem("user", JSON.stringify(user));

eg:資料沒有序列化,導致儲存的資料異常:
圖片
2)獲取資料:getItem(key)。如果 key 對應的 value 獲取不到,則返回值是 null。

const usernameLocal = localStorage.getItem("username");

const usernameSession = sessionStorage.getItem("username");

// 獲取到的資料為string,使用時反序列化資料

const userLocal = JSON.parse(localStorage.getItem("user"));

const userSession = JSON.parse(sessionStorage.getItem("user"));
3)刪除資料:removeItem(key);
localStorage.removeItem("username");

sessionStorage.removeItem("username");
4)清空資料:clear();
localStorage.clear();

sessionStorage.clear();
5)在不確定是否存在 key 的情況下,可以使用 hasOwnProperty() 進行檢查;
localStorage.hasOwnProperty("userName"); // true

sessionStorage.hasOwnProperty("userName"); // false
6)當然,也可以使用 Object.keys() 檢視所有儲存資料的鍵;
Object.keys(localStorage); // ['username']

Object.keys(sessionStorage);

3.2 瀏覽器檢視

本地儲存的內容可以在瀏覽器中直接檢視,以 Chrome 為例,按住鍵盤 F12 進入開發者工具後,選擇 Application,然後就能在左邊 Storage 列表中找到 localStorage 和 sessionStorgae。
圖片

3.3 監聽資料變化

當儲存的資料發生變化時,其他頁面透過監聽 storage 事件,來獲取變更前後的值,以及根據值的變化來處理頁面的展示邏輯。JS 原生監聽事件,只能夠監聽同源非同一個頁面中的 storage 事件,如果想監聽同一個頁面的,需要改寫原生方法,丟擲自定義事件來監聽。
具體如下:
1)監聽同源非同一個頁面:

直接在其他頁面新增監聽事件即可。

eg:同域下的 A、B 兩個頁面,A 修改了 localStorage,B 頁面可以監聽到 storage 事件。

window.addEventListener("storage", () => {

// 監聽 username 值變化

if (e.key === "username") {

console.log("username 舊值:" + e.oldValue + ",新值:" + e.newValue);

}

})

注:

1)當兩次 setItem 更新的值一樣時,監聽方法是不會觸發的;
2)storage 事件只能監聽到 localStorage 的變化。
2)監聽同一個頁面:

重寫 Storage 的 setItem 事件,同理,也可以監聽刪除事件 removeItem 和獲取事件 getItem。

(() => {

const originalSetItem = localStorage.setItem;

// 重寫 setItem 函式

localStorage.setItem = function (key, val) {

let event = new Event("setItemEvent");

event.key = key;

event.newValue = val;

window.dispatchEvent(event);

originalSetItem.apply(this, arguments);

};

})();

window.addEventListener("setItemEvent", function (e) {

// 監聽 username 值變化

if (e.key === "username") {

const oldValue = localStorage.getItem(e.key);

console.log("username 舊值:" + oldValue + ",新值:" + e.newValue);

}

});

4、儲存容量上限到底是不是5MB?

瀏覽器預設能夠儲存 5M 的資料,但實際上,瀏覽器並不會為其分配特定的儲存空間,而是根據當前瀏覽器的空閒空間來判斷能夠分配多少儲存空間。

可以使用 Storage 的 length 屬性,對儲存容量進行測算。

以 localStorage 為例:

let str = "0123456789";

let temp = "";

// 先生成一個 10KB 的字串

while (str.length !== 10240) {

str = str + "0123456789";

}

// 清空

localStorage.clear();

// 計算總量

const computedTotal = () => {

return new Promise((resolve) => {

// 往 localStorage 中累積儲存 10KB

const timer = setInterval(() => {

  try {

    localStorage.setItem("temp", temp);

  } catch (e) {

    // 報錯說明超出最大儲存

    resolve(temp.length / 1024);

    clearInterval(timer);

    // 統計完記得清空

    localStorage.clear();

  }

  temp += str;

}, 0);

});

};

// 計算使用量

const computedUse = () => {

let cache = 0;

for (let key in localStorage) {

if (localStorage.hasOwnProperty(key)) {

  cache += localStorage.getItem(key).length;

}

}

return (cache / 1024).toFixed(2);

};

(async () => {

const total = await computedTotal();

let use = "0123456789";

for (let i = 0; i < 1000; i++) {

use += "0123456789";

}

localStorage.setItem("use", use);

const useCache = computedUse();

console.log(最大容量${total}KB);

console.log(已用${useCache}KB);

console.log(剩餘可用容量${total - useCache}KB);

})();

可見在 Chrome 瀏覽器下,localStorage 有 5M 容量:
圖片

5、用好本地儲存的一些建議

在某些特殊場景下,需要儲存大資料,為了更好的利用 Storage 的儲存空間,可以採取以下解決方案,但不應該過於頻繁地將大量資料儲存在 Storage 中,因為在寫入資料時,會對整個頁面進行阻塞(不推薦這種方式)。

5.1壓縮資料

可以使用資料壓縮庫對 Storage 中的資料進行壓縮,從而減小資料佔用的儲存空間。eg:使用 lz-string 庫的 compress() 函式將資料進行壓縮,並將壓縮後的資料儲存到 localStorage 中。

const LZString = require("lz-string");

const data = "This is a test message";

// 壓縮

const compressedData = LZString.compress(data);

localStorage.setItem("test", compressedData);

// 解壓

const decompressedData = LZString.decompress(localStorage.getItem("test"));

5.2分割資料

將大的資料分割成多個小的片段儲存到 Storage 中,從而減小單個資料佔用的儲存空間。

eg:將使用者資料分割為單項儲存到 localStorage 中。

for (const key in userInfo) {

localStorage.setItem(key, userInfo[key]);

}

圖片

5.3取消不必要的資料儲存

可以在程式碼中取消一些不必要的資料儲存,從而減小佔用空間。eg:只儲存用到的使用者名稱、公司主體和後端所在環境欄位資訊。

for (const key in userInfo) {

if (["userName", "legalEntityName", "isOnline"].includes(key)) {

localStorage.setItem(key, userInfo[key]);

}

}

圖片

5.4設定過期時間

localStorage 是不支援過期時間的,在儲存資訊過多後,會拖慢瀏覽器速度,也會因為瀏覽器儲存容量不夠而報錯,可以封裝一層邏輯來實現設定過期時間,以達到清理的目的。

// 設定

function set(key, value){

const time = new Date().getTime(); //獲取當前時間

localStorage.setItem(key, JSON.stringify({value, time})); //轉換成json字串

}

// 獲取

function get(key, exp){

// exp 過期時間

const value = localStorage.getItem(key);

const valueJson = JSON.parse(value);

//當前時間 - 儲存的建立時間 > 過期時間

if(new Date().getTime() - valueJson.time > exp){

console.log("expires"); //提示過期

} else {

console.log("value:" + valueJson.value);

}

}

6、實踐應用中的案例參考

6.1 使用習慣記錄

用來快取一些篩選項資料,儲存使用者習慣資訊,起到避免多次重複操作的作用。eg:在 beetle 工程列表中,展示了自已許可權下所有 beetle 的專案,對於參與專案多和參與專案少的人,操作習慣是不同的,由此,記錄收藏功能狀態解決了這一問題,讓篩選項記住使用者選擇,方便下次使用。
圖片

圖片
  在開發使用中,直接獲取 localStorage.getItem('isFavor') 作為預設值展示,切換後使用 localStorage.setItem() 方法更新儲存內容。

// 獲取

const isFavor = localStorage.getItem('isFavor');

this.state = {

isFavor: isFavor !== null ? Number(isFavor) : EngineeringTypeEnum.FAVOR,

};

// 展示預設值

<Form.Item name='isFavor' initialValue={this.state.isFavor}>

<Select

placeholder='篩選收藏的工程'

onChange={(e) => this.changeFavor(e)}

{...searchSmallFormProps}
  {EngineeringTypeEnum.property.map(e => (<Option key={e.id} value={e.id}>{e.name}</Option>))}

</Select>

</Form.Item>

// 變更

changeFavor = (e) => {

localStorage.setItem('isFavor', e);

this.setState({ isFavor: e });

};

6.2 首次開啟提示

用來快取使用者導覽,尤其是隻需要出現一次的操作說明彈窗等。

eg:當第一次或者清空快取後登入,頁面需要出現操作指南和使用者手冊的彈窗說明。

圖片
在開發使用中,注意儲存的資料型別為 string,轉成布林值是為了在外掛中方便控制彈窗的顯示隱藏。

// 獲取

const operationVisible = localStorage.getItem('operationVisible');

this.state = {

operationVisible: operationVisible === null || operationVisible === 'true' ? true : false,

};

// 控制展示

<Modal

title='操作指南'

open={this.state.operationVisible}

onCancel={() => {

this.setState({ operationVisible: false });

localStorage.setItem('operationVisible', false);

}}

footer={null}

destroyOnClose={true}

<Divider orientation='left'>動作</Divider>

<p>介面 》 用例 》 用例集,3級結構滿足不了後續的使用,因此增加【動作】這一層級,【動作】是介面測試的最小單元,多個【動作】可以組合成一個用例,多個用例可以聚合為用例集;</p>

<Image src={OperationGuidePng} preview={false} />

</Modal>

6.3 減少重複訪問介面

在瀏覽頁面時,會遇到一些經常訪問但返回資料不更新的介面,這種特別適合用做頁面快取,只在頁面開啟的時候訪問一次,其他時間獲取快取資料即可。

eg:在我們的一些內部系統中,使用者資訊是每個頁面都要用到的,尤其是 userId 欄位,會與每個獲取資料介面掛鉤,但這個資料是不會變的,一直請求是沒有意義的,為減少介面的訪問次數,可以將主要資料快取在 localStorage 內,方便其他介面獲取。

7、本文小結

希望透過此篇文章,可以讓大家瞭解 Web Storage 在瀏覽器資料儲存和讀取的相關操作,以及相關事件和限制。

它可以用於儲存使用者的偏好設定、表單資料等,在開發中使用可以方便的儲存和讀取資料,提高使用者體驗。當然,在使用時需要特別注意它的限制,以及在儲存、讀取和刪除資料過程中的錯誤處理。

8、參考資料

[1] 新手入門貼:史上最全Web端即時通訊技術原理詳解

[2] Web端即時通訊技術盤點:短輪詢、Comet、Websocket、SSE

[3] 一文讀懂前端技術演進:盤點Web前端20年的技術變遷史

[4] Web端即時通訊基礎知識補課:一文搞懂跨域的所有問題!

[5] Web端即時通訊實踐乾貨:如何讓你的WebSocket斷網重連更快速?

[6] WebSocket從入門到精通,半小時就夠!

[7] WebSocket硬核入門:200行程式碼,教你徒手擼一個WebSocket伺服器

[8] 長連線閘道器技術專題(四):愛奇藝WebSocket實時推送閘道器技術實踐

[9] 網頁端IM通訊技術快速入門:短輪詢、長輪詢、SSE、WebSocket

[10] 搞懂現代Web端即時通訊技術一文就夠:WebSocket、socket.io、SSE

[11] IM跨平臺技術學習(一):快速瞭解新一代跨平臺桌面技術——Electron

[12] Wasm在即時通訊IM場景下的Web端應用效能提升初探

[13] 一套海量線上使用者的移動端IM架構設計實踐分享(含詳細圖文)

[14] 一套億級使用者的IM架構技術乾貨(上篇):整體架構、服務拆分等

[15] 一套億級使用者的IM架構技術乾貨(下篇):可靠性、有序性、弱網最佳化等

[16] 從新手到專家:如何設計一套億級訊息量的分散式IM系統

[17] 新手入門一篇就夠:從零開發移動端IM

(本文已同步釋出於:http://www.52im.net/thread-4745-1-1.html

相關文章