localStorage 存滿了怎么辦?


先來幾道面試題

1、a.meituan.com 和 b.meituan.com 這兩個域能夠共享同一個 localStorage 嗎?

2、在 webview 中打開一個頁面:i.meituan.com/home.html,點擊一個按鈕,調用 js 橋打開一個新的 webview:i.meituan.com/list.html,這兩個分屬不同 webview 的頁面能共享同一個 localStorage 嗎?

3、如果 localStorage 存滿了,再往里存東西,或者要存的東西超過了剩余容量,會發生什么?

答案

1、同一個域名(document.domain)共享同一個 localStorage,a.meituan.com 和 b.meituan.com 是兩個域名,所以不能共享

2、能。相當於同一個瀏覽器的不同標簽頁。不同瀏覽器之間不能共享。

3、存不進去並報錯(QuotaExceededError)

 

技術很簡單,業務很麻煩

在大公司,同一個域名下可能存在幾十上百條業務線,每條業務線都可能因為各種理由往 localStorage 里塞東西,跨頁面傳數據啦、緩存啦、離線化啦、性能優化啦...,5M 看起來很多,其實很快就用完了。而開發時基本無感知,是因為大家都只訪問自己的業務,但用戶會訪問各種業務,時間一久,很容易就存滿了,凡是嚴重依賴 localStorage 的業務流程都存在風險,寫可能出問題,讀自然也會出問題。

一種容易想到的方案是,當 localStorage 存滿后降級到 sessionStorage 里。看上去沒啥問題,但實際業務中 app 內 h5 頁面跳轉常常采用新打開 webview 的方式,這么做的好處是關閉一個 webview 可以直接回到上一個頁面,而不用重新加載頁面,對於訂單填寫這類帶有狀態的頁面就很需要這么做。新打開 webview 等於新打開一個會話,而 sessionStorage 只能存在於同一個會話中,因此 sessionStorage 無法跨頁面共享。

那降級到 cookie 里呢?cookie 一共才 50 個,總大小不超過 4k,作為 backup 過於脆弱,而且還會影響請求的效率。如果后端對請求頭大小做了限制,還可能產生 413 錯誤,導致請求被攔截。

那降級到 url 上呢?很麻煩。比如有一個交互流程是這樣的:頁面 A => 頁面 B => 頁面 C,如果頁面 A 的數據要傳到頁面 C,就得通過頁面 B 做一層中轉。而且 url 長度也是有限制的。

單頁應用解決跨頁面傳數據就很簡單,改造成單頁應用呢?這個就得估算成本,看老板們認不認可了,而且原有應用積累了大量的業務邏輯,沒有注釋,沒有測試用例,需求文檔散落在不知名的角落,你真能保證重新做的和原來的功能一模一樣嗎。

我們還可以求助客戶端同學,通過 js bridge 提供一個仿 localStorage 的東西,不過要考慮版本的問題,新版 app 里使用了客戶端提供的 store,怎么兼顧老版 app,而且還要考慮兼容瀏覽器、微信。所以這種方案也只能解決一部分問題,當然,如果 h5 的流量絕大多數都在 app 里,那么這種方案是可以解決一大部分問題的,不過客戶端提供的存儲可不見得比原生的存儲可靠,還是得加 backup。

我們還可以求助后端同學,多加幾個字段甚至多加幾個接口,不過這涉及到核心業務流程的改造,風險不小,而且不見得能完全解決問題,也無法永久的解決問題。

我們還可以來一招互相傷害大法,那就是把別人存的東西都刪掉。。。

localStorage 是個好東西,不用,這是因噎廢食,用,又很難統一和約束各業務線的用法,一旦放開用,就總會面臨存滿的風險。跟你在同一個域名下做開發的人可能跟你不在同一棟樓,甚至可能不在同一個城市,你有那個影響力去統一所有人的使用規范嗎。

還有一個很討厭的事情:safari 在隱私模式下不支持 localStorage 的存取(ios11 以下),這種情況比較罕見,但如果出了客訴,也是個大坑。

 

問題的本質

localStorage 歸根結底就兩個作用:持久化存儲與跨頁面傳數據。持久化存儲不會出問題,存不進去就存不進去唄,取不出來就去其它地方取,或者不取。問題就出在跨頁面傳數據上,上一個頁面因為 localStorage 存滿導致數據沒有寫入,下一個頁面讀取數據為空,從而導致錯誤。

 

問題的根源

同一個域名共享同一個 localStorage,而同一個域名下存在過多獨立的業務線,業務線之間各自為政,毫無節制的攫取公共資源,這就是 localStorage 溢出問題的根源。

就我觀察的情況來看,很多公司都喜歡把 h5 頁面掛在 i.xxx.com 或 m.xxx.com 下,然后通過路徑划分業務,比如 i.xxx.com/project-a, i.xxx.com/project-b...,隨着業務發展,越來越多的業務都加到 i.xxx.com 中,“公地悲劇”就無可奈何的產生了,而且積重難返。我以前在的團隊也是如此,用 h5、js、css 這樣的類型名稱來划分目錄,初期東西少,自然沒問題,但后來所有應用都把資源塞到 js 文件夾、css 文件夾下,一個文件夾包含了來自五湖四海的上百個文件,維護起來十分難受。

通過應用類型划分,而不是通過業務類型划分,這是最初架構策略的問題。如果 a 業務掛在 a.xxx.com 下,b 業務掛在 b.xxx.com 下,每個業務有獨立的團隊維護,localStorage 從公共資源變成團隊的私有財產,或許這樣才能從根源上解決 localStorage 無限膨脹的問題。有網友提出對 i.xxx.com 進一步划分子域,其實也是這個思路。

 

理想的方案

假設我們回到起點,從零建設前端工程,我們怎么避免 localStorage 存滿的問題?

1、划分域名。各域名下的存儲空間由各業務組統一規划使用

2、跨頁面傳數據:考慮單頁應用、優先采用 url 傳數據

3、最后的兜底方案:清掉別人的存儲

 

互相傷害其實是個好辦法

在已然發展很久的業務中,我們怎么解決此問題呢?

const QUOTA_EXCEEDED_ERR_CODE = 22
function write (key, data) {
    try {
        localStorage.setItem(key, data);
    } catch (e) {
        if (e.code === QUOTA_EXCEEDED_ERR_CODE) {
            localStorage.clear();
            localStorage.setItem(key, data);
        }
    }
}

上面這個方法還是有點問題,因為它把自己業務要用的東西也給刪了,所以自己的業務最好統一在 key 上加一個前綴,清空 localStorage 時只刪別人的。

有的同學可能會擔心,這樣會不會對其它業務造成傷害?或者產生一些難以發現的 bug。其實這種擔心很大程度上是因為忽略了實際的使用場景。用戶用同一個設備打開同一個 app,在同一個時間只能訪問一個業務,因此不會存在某個業務正在使用過程中,localStorage 被其它業務清掉的場景,除非!除非有交叉的業務場景。

沒有銀彈,沒有十全十美的方案。

 

補充

掘金網友@FE 提出用 indexedDB 存文件類型的數據,localStorage 存業務數據,這是一種可以緩解問題的方案。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM