Jack Jiang

          我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
          posts - 499, comments - 13, trackbacks - 0, articles - 1

          本文由轉(zhuǎn)轉(zhuǎn)技術(shù)團(tuán)隊劉筱雨分享,原題“一文讀懂瀏覽器本地存儲:Web Storage”,下文進(jìn)行了排版和內(nèi)容優(yōu)化。

          1、引言

          鑒于目前瀏覽器技術(shù)的進(jìn)步(主要是HTML5的普及),在Web網(wǎng)頁端IM聊天應(yīng)用的技術(shù)選型階段,很多開發(fā)者都會糾結(jié)到底該不該像原生移動端IM那樣將聊天記錄緩存在瀏覽器的本地,還是像傳統(tǒng)Web端即時通訊那樣繼續(xù)存儲在服務(wù)端?本文將為你簡潔明了地講清楚瀏覽器本地存儲技術(shù)(Web Storage),然后你就知道到底該怎么選擇了。

          瀏覽器本地存儲是指瀏覽器提供的一種機(jī)制,允許 Web 應(yīng)用程序在瀏覽器端存儲數(shù)據(jù),以便在用戶下次訪問時可以快速獲取和使用這些數(shù)據(jù)。一共兩種存儲方式:localStorage 和 sessionStorage,本文將主要圍繞這兩種技術(shù)來進(jìn)行總結(jié)。

           

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

          2.1區(qū)別

          localStorage 和 sessionStorage 的主要區(qū)別是生命周期。

          具體區(qū)別如下:

          容量限制的目的是防止濫用本地存儲空間,導(dǎo)致用戶瀏覽器變慢。

          2.2瀏覽器兼容性

          1)現(xiàn)在的瀏覽器基本上都是支持這兩種 Storage 特性的。

          各瀏覽器支持版本如下:

          2)如果使用的是老式瀏覽器,比如Internet Explorer 6、7 或者其他,就需要在使用前檢測瀏覽器是否支持本地存儲或者是否被禁用。

          以 localStorage 為例:

          if(window.localStorage){

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

          } else {

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

          }

          3)某些瀏覽器版本使用過程中,會出現(xiàn) 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 提供了相同的方法進(jìn)行存儲、檢索和刪除。

          常用的方法如下:

          1)設(shè)置數(shù)據(jù):setItem(key, value)。存儲的值可以是字符串、數(shù)字、布爾、數(shù)組和對象。對象和數(shù)組必須轉(zhuǎn)換為 string 進(jìn)行存儲。JSON.parse() 和 JSON.stringify() 方法可以將數(shù)組、對象等值類型轉(zhuǎn)換為字符串類型,從而存儲到 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:數(shù)據(jù)沒有序列化,導(dǎo)致保存的數(shù)據(jù)異常:

          2)獲取數(shù)據(jù):getItem(key)。如果 key 對應(yīng)的 value 獲取不到,則返回值是 null。

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

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

          // 獲取到的數(shù)據(jù)為string,使用時反序列化數(shù)據(jù)

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

          const userSession = JSON.parse(sessionStorage.getItem("user"));

          3)刪除數(shù)據(jù):removeItem(key);

          localStorage.removeItem("username");

          sessionStorage.removeItem("username");

          4)清空數(shù)據(jù):clear();

          localStorage.clear();

          sessionStorage.clear();

          5)在不確定是否存在 key 的情況下,可以使用 hasOwnProperty() 進(jìn)行檢查;

          localStorage.hasOwnProperty("userName"); // true

          sessionStorage.hasOwnProperty("userName"); // false

          6)當(dāng)然,也可以使用 Object.keys() 查看所有存儲數(shù)據(jù)的鍵;

          Object.keys(localStorage); // ['username']

          Object.keys(sessionStorage);

          3.2 瀏覽器查看

          本地存儲的內(nèi)容可以在瀏覽器中直接查看,以 Chrome 為例,按住鍵盤 F12 進(jìn)入開發(fā)者工具后,選擇 Application,然后就能在左邊 Storage 列表中找到 localStorage 和 sessionStorgae。

          3.3 監(jiān)聽數(shù)據(jù)變化

          當(dāng)存儲的數(shù)據(jù)發(fā)生變化時,其他頁面通過監(jiān)聽 storage 事件,來獲取變更前后的值,以及根據(jù)值的變化來處理頁面的展示邏輯。

          JS 原生監(jiān)聽事件,只能夠監(jiān)聽同源非同一個頁面中的 storage 事件,如果想監(jiān)聽同一個頁面的,需要改寫原生方法,拋出自定義事件來監(jiān)聽。

          具體如下:

          1)監(jiān)聽同源非同一個頁面:

          直接在其他頁面添加監(jiān)聽事件即可。

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

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

            // 監(jiān)聽 username 值變化

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

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

            }

          })

          注:

          • 1)當(dāng)兩次 setItem 更新的值一樣時,監(jiān)聽方法是不會觸發(fā)的;
          • 2)storage 事件只能監(jiān)聽到 localStorage 的變化。

          2)監(jiān)聽同一個頁面:

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

          (() => {

            const originalSetItem = localStorage.setItem;

            // 重寫 setItem 函數(shù)

            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) {

            // 監(jiān)聽 username 值變化

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

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

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

            }

          });

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

          瀏覽器默認(rèn)能夠存儲 5M 的數(shù)據(jù),但實際上,瀏覽器并不會為其分配特定的存儲空間,而是根據(jù)當(dāng)前瀏覽器的空閑空間來判斷能夠分配多少存儲空間。

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

          以 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);

                  // 統(tǒng)計完記得清空

                  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、用好本地存儲的一些建議

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

          5.1壓縮數(shù)據(jù)

          可以使用數(shù)據(jù)壓縮庫對 Storage 中的數(shù)據(jù)進(jìn)行壓縮,從而減小數(shù)據(jù)占用的存儲空間。

          eg:使用 lz-string 庫的 compress() 函數(shù)將數(shù)據(jù)進(jìn)行壓縮,并將壓縮后的數(shù)據(jù)存儲到 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分割數(shù)據(jù)

          將大的數(shù)據(jù)分割成多個小的片段存儲到 Storage 中,從而減小單個數(shù)據(jù)占用的存儲空間。

          eg:將用戶數(shù)據(jù)分割為單項存儲到 localStorage 中。

          for (const key in userInfo) {

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

          }

          5.3取消不必要的數(shù)據(jù)存儲

          可以在代碼中取消一些不必要的數(shù)據(jù)存儲,從而減小占用空間。

          eg:只存儲用到的用戶名、公司主體和后端所在環(huán)境字段信息。

          for (const key in userInfo) {

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

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

            }

          }

          5.4設(shè)置過期時間

          localStorage 是不支持過期時間的,在存儲信息過多后,會拖慢瀏覽器速度,也會因為瀏覽器存儲容量不夠而報錯,可以封裝一層邏輯來實現(xiàn)設(shè)置過期時間,以達(dá)到清理的目的。

          // 設(shè)置

          function set(key, value){

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

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

          }

          // 獲取

          function get(key, exp){

            // exp 過期時間

            const value = localStorage.getItem(key);

            const valueJson = JSON.parse(value);

            //當(dāng)前時間 - 存儲的創(chuàng)建時間 > 過期時間

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

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

            } else {

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

            }

          }

          6、實踐應(yīng)用中的案例參考

          6.1 使用習(xí)慣記錄

          用來緩存一些篩選項數(shù)據(jù),保存用戶習(xí)慣信息,起到避免多次重復(fù)操作的作用。

          eg:在 beetle 工程列表中,展示了自已權(quán)限下所有 beetle 的項目,對于參與項目多和參與項目少的人,操作習(xí)慣是不同的,由此,記錄收藏功能狀態(tài)解決了這一問題,讓篩選項記住用戶選擇,方便下次使用。

           

           

          在開發(fā)使用中,直接獲取 localStorage.getItem('isFavor') 作為默認(rèn)值展示,切換后使用 localStorage.setItem() 方法更新保存內(nèi)容。

          // 獲取

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

          this.state = {

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

          };

          // 展示默認(rèn)值

          <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 首次打開提示

          用來緩存用戶導(dǎo)覽,尤其是只需要出現(xiàn)一次的操作說明彈窗等。

          eg:當(dāng)?shù)谝淮位蛘咔蹇站彺婧蟮卿洠撁嫘枰霈F(xiàn)操作指南和用戶手冊的彈窗說明。

          在開發(fā)使用中,注意存儲的數(shù)據(jù)類型為 string,轉(zhuǎn)成布爾值是為了在插件中方便控制彈窗的顯示隱藏。

          // 獲取

          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級結(jié)構(gòu)滿足不了后續(xù)的使用,因此增加【動作】這一層級,【動作】是接口測試的最小單元,多個【動作】可以組合成一個用例,多個用例可以聚合為用例集;</p>

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

          </Modal>

          6.3 減少重復(fù)訪問接口

          在瀏覽頁面時,會遇到一些經(jīng)常訪問但返回數(shù)據(jù)不更新的接口,這種特別適合用做頁面緩存,只在頁面打開的時候訪問一次,其他時間獲取緩存數(shù)據(jù)即可。

          eg:在我們的一些內(nèi)部系統(tǒng)中,用戶信息是每個頁面都要用到的,尤其是 userId 字段,會與每個獲取數(shù)據(jù)接口掛鉤,但這個數(shù)據(jù)是不會變的,一直請求是沒有意義的,為減少接口的訪問次數(shù),可以將主要數(shù)據(jù)緩存在 localStorage 內(nèi),方便其他接口獲取。

          7、本文小結(jié)

          希望通過此篇文章,可以讓大家了解 Web Storage 在瀏覽器數(shù)據(jù)存儲和讀取的相關(guān)操作,以及相關(guān)事件和限制。

          它可以用于保存用戶的偏好設(shè)置、表單數(shù)據(jù)等,在開發(fā)中使用可以方便的存儲和讀取數(shù)據(jù),提高用戶體驗。當(dāng)然,在使用時需要特別注意它的限制,以及在存儲、讀取和刪除數(shù)據(jù)過程中的錯誤處理。

          8、參考資料

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

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

          [3] 一文讀懂前端技術(shù)演進(jìn):盤點Web前端20年的技術(shù)變遷史

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

          [5] Web端即時通訊實踐干貨:如何讓你的WebSocket斷網(wǎng)重連更快速?

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

          [7] WebSocket硬核入門:200行代碼,教你徒手?jǐn)]一個WebSocket服務(wù)器

          [8] 長連接網(wǎng)關(guān)技術(shù)專題(四):愛奇藝WebSocket實時推送網(wǎng)關(guān)技術(shù)實踐

          [9] 網(wǎng)頁端IM通信技術(shù)快速入門:短輪詢、長輪詢、SSE、WebSocket

          [10] 搞懂現(xiàn)代Web端即時通訊技術(shù)一文就夠:WebSocket、socket.io、SSE

          [11] IM跨平臺技術(shù)學(xué)習(xí)(一):快速了解新一代跨平臺桌面技術(shù)——Electron

          [12] Wasm在即時通訊IM場景下的Web端應(yīng)用性能提升初探

          [13] 一套海量在線用戶的移動端IM架構(gòu)設(shè)計實踐分享(含詳細(xì)圖文)

          [14] 一套億級用戶的IM架構(gòu)技術(shù)干貨(上篇):整體架構(gòu)、服務(wù)拆分等

          [15] 一套億級用戶的IM架構(gòu)技術(shù)干貨(下篇):可靠性、有序性、弱網(wǎng)優(yōu)化等

          [16] 從新手到專家:如何設(shè)計一套億級消息量的分布式IM系統(tǒng)

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


          (本文已同步發(fā)布于:http://www.52im.net/thread-4745-1-1.html



          作者:Jack Jiang (點擊作者姓名進(jìn)入Github)
          出處:http://www.52im.net/space-uid-1.html
          交流:歡迎加入即時通訊開發(fā)交流群 215891622
          討論:http://www.52im.net/
          Jack Jiang同時是【原創(chuàng)Java Swing外觀工程BeautyEye】【輕量級移動端即時通訊框架MobileIMSDK】的作者,可前往下載交流。
          本博文 歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明出處(也可前往 我的52im.net 找到我)。


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          Jack Jiang的 Mail: jb2011@163.com, 聯(lián)系QQ: 413980957, 微信: hellojackjiang
          主站蜘蛛池模板: 讷河市| 刚察县| 双峰县| 巢湖市| 曲周县| 浮梁县| 平江县| 芜湖市| 孟连| 连州市| 南平市| 屏东县| 庄浪县| 庐江县| 安宁市| 巩义市| 越西县| 米泉市| 镶黄旗| 龙泉市| 铜山县| 来安县| 慈溪市| 尉氏县| 芜湖县| 吕梁市| 青河县| 娄底市| 武汉市| 广饶县| 平顶山市| 芜湖市| 吉林市| 鹤庆县| 商水县| 沙河市| 都江堰市| 永嘉县| 剑阁县| 翁牛特旗| 会理县|