基於本地存儲的同源跨頁面數據同步方案

原創不易,但願能關注下咱們,再順手點個贊~~

本文首發於政採雲前端團隊博客: 基於本地存儲的同源跨頁面數據同步方案javascript

背景

提起這個方案,還要從某個風和日麗的早晨提及。那日小編正忙着手上的各類需求,忽然後端的親火急火燎的找到小編,說是有一個重要的用戶,在使用 Word 在線編輯文檔功能時,發現保存的文件被篡改了。一聽到這,我心想這下攤上事兒了,妥妥的線上故障,但仍是故做鎮定的開始排查是什麼問題。前端

通過了日以繼夜的排查後,小編髮現是因爲用戶同時打開了兩個在線編輯頁面,而且在 A 頁面的在線編輯工具還未關閉的狀況下,去 B 頁面也打開了在線編輯工具。java

說到這個在線編輯工具,它叫 pageOffice,當他在線被觸發啓動時,會在本地打開一個相似軟件的窗口,啓動一個相對獨立的服務。且這個服務前端經過 Web SDK 提供的 API 能進行控制的餘地很是小,惟一的通訊方式只有 pageOffice 中操做觸發頁面上的回調函數。在和 pageOffice 的客服進行了一系列如同太極的溝通後,咱們仍是沒能解決如何知道用戶已經打開了 pageOffice 而且阻止用戶在另外一個頁面觸發打開工具的方法。後端

初探

上文提到的, pageOffice 打開之後就成爲了相對獨立的個體,因而乎,小編對它直接的各類軟磨硬泡都宣告失敗。進而小編放棄了探索對它的控制,轉而思考兩個頁面之間通訊的控制。數組

平時我們對一個方法是否運行過,最經常使用的方式就是 「狀態開關」。即存儲用一個變量,相似於 ifOpen 之類的,將其設置爲 ture 去記錄當前方法已運行,再在其運行結束時設置爲 false,便可完成一個閉環。而咱們此次除了以上條件,還須要讓別的頁面也擁有這個變量,才能阻止別的頁面在這個方法運行時再次觸發這個方法。這聽起來有點繞,不過下面有一個小圖解來解釋咱們此次問題的初步解決方案。瀏覽器

未命名文件

顯而易見的,此處應有一個跨頁面通訊的方案,可是因爲這是同一個頁面上的功能,因此咱們能夠選擇最簡便的方案。緩存

提到跨頁面數據存儲,聰明的大家確定會想到本地存儲 localStorage,提到localStorage 小編就會想起它的兄弟 sessionStorage,那就大體回顧一下它們兩的特性吧:性能優化

  • localStorage: 持久的,相同的協議、主機名、端口(同源)能增刪改查,數據不會自動清除;
  • sessionStorage:臨時的,除了同源外還要在同一窗口下才能增刪改查,數據會在窗口關閉時自動清除。

看到這裏想必你們已經看出來,本地存儲 localStorage 徹底能夠知足上圖中描述的功能。可是回想一下題目中提到的 「反作用」 一詞,你們是否心中暗想此事必不簡單。session

小編解釋一下:首先,因爲 localStorage 不會自動清除的特性,當用戶再次進入頁面時,以前保存的 localStorage 裏的數據會還在;其次,以前提到過,pageOffice 打開後就獨立了,因此,這兩個條件結合後就存在這樣一個場景 —— 在 pageOffice 還在打開的時候,用戶先把頁面關閉了,以後再關閉 pageOffice,此時,頁面已經不存在了,因此 pageOffice 關閉時觸發的回調函數,此時已經通知不到頁面去改變存在 localStorage 裏的變量。而再下一次打開頁面時,因爲localStorage 存的數據仍是上次未關閉 pageOffice 時的 ifOpen = true, 因此,若是用戶不自主清除本地緩存,將再也打不開 pageOffice,小編把這種關閉頁面在將來可能會形成負面影響的數據稱爲 「反作用」。前端工程師

構思

爲了清除上述方案帶來的反作用,小編廢寢忘食圍繞反作用刪除的時機想到了幾種方案:

方案一:用 localStorage 儲存一條有當前打開頁面 Id 的數組,當頁面關閉就過濾掉關閉頁面的 Id,關閉頁面直到最後數組長度爲 1,而且 Id 就是當前頁面的 Id 時,就清除掉localStorage 中反作用的數據。

這個方案的缺陷就是,咱們沒法肯定頁面的關閉時機,現有的在頁面關閉時能觸發的事件是beforeunload,可是很是不理想的是,這個事件在頁面刷新的時候也會觸發,若是刷新頁面則會產生預期外的效果,這並非咱們想要的,即便在這個事件中區分當前觸發的是刷新仍是關閉也是不太合理的,全部最後仍是選擇更換別的方案。

方案二:因爲關閉頁面的時機沒法肯定,因此小編考慮將其轉存爲頁面上的變量或者換一種儲存方式。

查閱了和 localStorage 有關的內容以後,發現現存有這麼一個神奇的事件叫作 storage 事件,仔細閱讀關於這個事件的相關文獻後發現其有幾個特色:

  • 首先,它須要在同一瀏覽器打開兩個同源的頁面

  • 其次,兩個頁面都註冊了這個事件,而且有 localStorage 的變化,事件在其餘頁面返回最新變化的 localStorage 的 Key 和 Value

  • 最後,這個事件並非用來監聽當前頁面本身的 localStorage 變化的

看起來這個事件徹底就是考慮到了咱們轉存 localStorage 準備的,小編內心頓時就以爲怎麼會有這麼善解人意的事件。

雖然有了這個事件的存在,可是咱們該如何順利的幫助 localStorage 轉型呢?

回想起上文提到的 sessionStorage 這個會話存儲,一想到它可以在窗口關閉時自動清除,小編就想用它搞點事情。順便一提,頁面上的變量也是能夠在頁面關閉時自動清除的,不過當沒有兩個頁面的時候,這種事件觸發的變量一刷新就會丟失,可是 sessionStorage 刷新仍是會保留在當前頁面存儲中,因而,小編就萌生了這樣一個 localStoragesessionStorage 聯合使用的想法。

實現

這個方案最終的目的就是要把 localStorage 中的數據都轉到 sessionStorage,簡單來講也就是跨頁面的 sessionStorage 的數據同步,而 localStorage 就是咱們跨頁面的一座橋樑。因此,方案基本的實現原理就是:當數據變化時,咱們首先要作的就是把數據存在當前頁的 sessionStorage 裏,並觸發一次 localStorage 的變化即存一次數據到localStorage 裏,經過 storage 將數據運輸到另外一個頁面。

值得注意是,localStorage 的轉型就是爲了刪除反作用,因此當把數據存入localStorage 後,下一步就是直接清除存入 localStorage 裏的數據。

在這裏小編封裝了一個函數,數據傳的是一個對象,這樣就能夠一次同步多個數據啦,先進入一下圖解環節,讓你們有個初步的理解。

流程

原理函數:

// 觸發事件,須要同步數據變化時的事件
function setSessionStorage(payload) {
  const data = JSON.stringify(payload);
  // 同步當前頁面數據變化
  sessionStorage.setItem('setSessionStorage', data);
  // 觸發localStorage的change事件將數據同步到其餘頁面
  localStorage.setItem('setSessionStorage', data);
  // 刪除反作用
  localStorage.removeItem('setSessionStorage');
}
複製代碼

而後就是咱們的橋樑 storage 核心事件的實現:

因爲把數據存入 localStorage 後,下一步就是直接清除存入 localStorage 裏的數據,清除 localStorage 也是會進入這個函數的,只要校驗此時的值爲空時不將數據同步便可。

// 監聽的storage變化的事件
function storageChange(e) {
  // 校驗null是爲了在清除localStorage時不產生效果
  const ifNull = e.newValue === null || e.newValue === 'null';
  // 獲取從別的頁面傳遞過來的數據,並將數據同步到當前頁
  if (e.key === 'setSessionStorage' && !ifNull) { 
    sessionStorage.setItem('setSessionStorage', e.newValue);
  }

  // 頁面初始化時觸發一次change事件將數據同步到其餘頁面
  if (e.key === 'getSessionStorage' && !ifNull) {
    // 獲取當前頁的sessionStorage
    const currentSessionStorage = sessionStorage.getItem('setSessionStorage');
    // 其餘頁面初始化時,已存在的標籤頁會觸發getSessionStorage事件
    // 將sessionStorage儲存在localStorage並觸發其餘頁面的change事件,同時傳遞參數
    localStorage.setItem('setSessionStorage', currentSessionStorage);
    localStorage.removeItem('setSessionStorage');
  }
}
複製代碼

這裏還有一點要注意的是,咱們同源跨頁面的場景通常兩個頁面都不是同時開啓的,又因爲咱們刪掉了 localStorage 裏的數據,因此,在另外一個頁面打開時,咱們須要進行一次數據的同步,這就是上文的 storage 事件中下部分函數的功能。這部分可能會有點繞,因此小編仍是貼心的準備了一份圖解供你們參考。

666
初始化函數部分:

function init() {
  // 初始化監聽localStorage的change事件
  window.addEventListener('storage', storageChange);
  // 頁面初始化時觸發一次change事件將數據同步到其餘頁面
  localStorage.setItem('getSessionStorage', 'any');
  // 刪除反作用
  localStorage.removeItem('getSessionStorage');
}
複製代碼

最後,無論在頁面哪一個地方,只要不關閉窗口,只須要一行獲取當前 sessionStorage 的代碼便可。

// 當前sessionStorage儲存的數據
const currentSessionStorage = sessionStorage.getItem('setSessionStorage');
複製代碼

這樣,一種簡單無反作用的同源跨頁面數據同步方法就實現啦~

感謝各位的閱讀,有任何建議和意見均可以在下方留言,小編定當積極改正。

招賢納士

招人,前端,隸屬政採雲前端大團隊(ZooTeam),50 餘個小夥伴正等你加入一塊兒浪~ 若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變「5年工做時間3年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手參與一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給ZooTeam@cai-inc.com

推薦閱讀

前端工程實踐之可視化搭建系統(一)

寫給前端工程師的 Serverless 入門

自動化 Web 性能優化分析方案

相關文章
相關標籤/搜索