靜態資源(JS/CSS)存儲在localStorage有什麼缺點?為什麼沒有被廣泛應用?
移動端完全沒有兼容問題,只要寫版本更新判斷就行。
PC端支持到IE8,不支持則請求。目前一個新項目準備這樣架構,但隱隱擔憂有什麼致命缺點,因為現在還沒有被廣泛應用。
謝邀。經常用,所以過來回答一下。
我的看法是:PC上用的價值不大,移動端單頁面應用(也有叫webapp)值得嘗試。
這裡要首先提出一個關於靜態資源管理和SEO(搜索引擎優化)方面的關聯問題:如果要做SEO,那麼CSS必然不能進行LS(localstorage)的本地緩存優化。這個原因很簡單:
要進行SEO,必須直接輸出完整HTML,因此必須讓樣式在頭部以link標籤載入。如果先輸出HTML,後用js從本地緩存讀取樣式再插入,會出現嚴重的阻塞和閃爍問題,相信正常人是不會這麼乾的。
然後再更正一件事,就是取出localstorage的代碼不一定要eval,eval很evil,一個eval函數很有可能影響整個js文件的壓縮(出現eval之後不能對變數名進行替換),當然,我們可以通過一些hack避免這種壓縮問題,不過我喜歡這樣搞:var script = document.createElement("script");
var code = "!function(){" + getCodeFromLocalStorage() + "
}();";
script.appendChild(document.createTextNode(code));
document.head.appendChild(script);
沒測過效率,應該跟eval沒多大差別,真正的性能損耗還是在LS的讀取上。
再來解答一個困惑:相比瀏覽器原生的緩存,LS還有什麼優勢呢?本來,最棒的瀏覽器緩存是本地強緩存,我在另外一個知乎答案中解釋過本地強緩存的終極用法:大公司里怎樣開發和部署前端代碼? - 張雲龍的回答 工程化實踐起來有一定的難度,我也在那篇回答里提到了,用戶主動觸發的頁面刷新行為(比如刷新按鈕、右鍵刷新、F5等),會導致瀏覽器放棄本地緩存,使用協商緩存(304緩存),用了LS之後,可以完全避免這種情況,等效於無視用戶主動刷新行為的本地強緩存。當LS+eval速度大於304協商速度時,LS方案具有統計上的正收益。
此外,討論緩存問題不能單看一次讀取,要從整個緩存的生命周期觀察。瀏覽器緩存以url為單位,一個url可能對應多個文件的打包,N個文件合併成一個url,假設每個文件更新的概率是P,那麼整個url緩存失效的概率就是,隨著合併文件的增多,每次上線url緩存失效的可能性會非常高,而如果採用LS,配合combo服務,加上精細到文件甚至字元級別的緩存控制,就能讓版本迭代過程中緩存的命中率大大提高。
還有,緩存問題也絕不是一個頁面的問題,網站很多頁面之間會跳轉訪問,彼此之間也有共享的靜態資源,基於url的緩存讓跨頁面之間緩存共享問題變得粗粒度。舉個例子,有A、B兩個頁面,彼此有訪問路徑(比如百度首頁和搜索結果頁之間的訪問),其中:- A頁面使用資源:a, b, c, d
- B頁面使用資源:a, b, c, e, f
假設不考慮並發請求的優化,我們希望儘可能的打包,再假設A頁面是主要入口,那麼,最合理的方案可能就是a-b-c-d打包(設為[abcd]),e-f打包(設為[ef]),從而使得:
- A頁面使用url:[abcd]
- B頁面使用url:[abcd]+[ef]
由於用戶大多首次訪問A頁面,然後會跳轉到B頁面,所以訪問A頁面會很快,再跳轉到B頁面可以從緩存中使用[abcd]包,再只需載入[ef]包即可。為了更大的緩存利用率,我們讓B頁面復用A頁面的url緩存,但多了一個不需要的d資源這也是合理的。也就是說,基於url的緩存利用可能在有些情況下會資源的冗餘載入。想想那些通過url直接訪問B頁面的用戶來說,無緩存情況下,頁面載入的是[abcd]+[ef]兩個資源包,既有冗餘,又是兩個請求,這並不是最理想的載入策略(這個方案是傾向於優化A頁面展現的,雖然B頁面首次展現不理想,但B頁面大部分pv是從A頁面導入,網站總體性能是更好的)。
而使用combo服務+LS的情況就不同了,假設combo的url的形式是[a,b,c,...],那麼單獨訪問A、B頁面的資源url就是:- A頁面使用url:[a,b,c,d]
- B頁面使用url:[a,b,d,e,f]
用戶由A頁面進入網站,載入[a,b,c,d]這個url,然後LS緩存4個資源,再跳轉到B頁面,緩存控制框架可以知道本地緩存了哪些,然後只發起[e,f]這個請求。其效果基本等效於瀏覽器基於URL的緩存。而對於那些沒有通過A頁面直接訪問B頁面的用戶來說,B頁面載入的是[a,b,d,e,f],也是不錯的合併策略。LS在這個時候就發揮了那麼一點點優勢。
當然,這種優勢還不夠明顯,最能展現LS優勢的,其實是單頁面應用。因為單頁面應用需要完全有JS管理頁面狀態,並增量載入資源,用戶也可能通過帶有hash的url直接訪問某個單頁面中的虛擬頁面,同一個頁面會有很多種不同的資源請求組合,這個時候,唯有LS+combo才能很好的解決資源載入與緩存問題。對於這種情況,我有一個網站可以用於展示效果:
Scrat - webapp模塊化開發體系
這是部署在github上的頁面,沒有combo服務,但是可以一定程度上展現LS緩存的效果,我把js、css都緩存到LS中了,有興趣的同學可以查看這個webapp中不同頁面間的 首次訪問、二次訪問、頁面間跳轉 等過程中資源的載入效果。這個網頁的源代碼在這裡:scrat-team/scrat-site · GitHub 通過travis-ci自動構建到這裡的:scrat-team/scrat-team.github.io · GitHub
總結一下
PC上應用價值不大的原因在於:- 兼容性不太好,不支持LS的瀏覽器比例仍然很大
- 網路速度快,協商緩存響應快,LS讀取+eval很多時候會比不上304
- 通常需要SEO,導致css不能緩存,僅緩存js使得整個緩存方案意義進一步減小
- 瀏覽器本地緩存足夠可靠持久
- 跨頁面間共享緩存即便有浪費也差別不大
移動端webapp值得一試的原因在於:
- 兼容性好
- 網速慢,LS讀取+eval大多數情況下快於304
- 都說是webapp了,不需要seo,css也可以緩存,再通過js載入
- 瀏覽器緩存經常會被清理,LS被清理的幾率低一些
- 以模塊文件為單位,緩存失效率低
- 不同頁面狀態直接訪問、二次訪問、頁面狀態跳轉資源組合是不確定的,不能通過url來緩存資源,否則就不「增量」啦
另外,LS緩存作為緩存,我們要先假設它是不可靠的,使用原則就是「LS緩存存在就用,沒有就直接載入」,就是所謂的平穩退化(或漸進增強)。那些「寫滿了」,「寫不了」,「被清了」等情況一概當做沒有緩存。從統計的角度來看,在合適的模式下開啟緩存全局總是有正收益的
順便做個廣告,以下UC的產品都是使用這套模塊化開發體系進行開發的,後端均為nodejs,UC的前端開發者大多為全棧工程師:- 神馬視頻
- NBA 常規賽2014-2015賽季
- 2014年巴西世界盃
還有一些UC only的產品就不貼了,不方便查看源代碼和網路情況。。。
最後感慨一下:===============[ 補充 ]===============收到一些反饋,這裡做一下補充:前端性能優化既是一個工程問題,又是一個統計問題。
- 這是一種「黑科技」,因為LS本身並不是被設計用來干這件事的。從過往歷史來看,任何黑科技都是短暫且不可靠的,但就在當下,我也想不到什麼更好的工程手段來提升移動端webapp的性能,所以,LS+combo的方案可以說是「有總比沒有強」
- 在未來,HTTP2時代的到來應該會完美絕殺這種黑科技,因此,工程化的具體實施方法必然要與時俱進,不過工程化的方法論不會過時,無論在哪個時代,我們都應該全面、科學的分析工程問題,結合當前的瀏覽器環境和技術手段來做方案,保持網站的性能
- 有安全領域大神 @EtherDream 指出,「靜態資源存 localStorage 就是水坑漏洞的前兆,風險遠遠大於優化」,這點我也認可,一旦有xss漏洞,就會被人利用將惡意代碼注入到LS中,導致即便修復了xss惡意代碼也存在的問題。所以我們現在採用的策略是每次部署新版本就會清除全部緩存。這會導致緩存利用率的下降,不過至少還有部分瀏覽器緩存在呢,算是一個折中處理。
- 騰訊網前端團隊做的 MT 方案利用LS把更新機制精細到了字元級別,這個確實更加「喪心病狂」,哈哈。不過看到他們運行的很不錯,應該是更加優秀的緩存控制方案。
- 可編程的緩存控制,我覺得這是一個值得深挖的方向,對前端的工程化價值非常大,尤其是與統計結合起來,根據網站的訪問統計、頁面間的步長關係計算出最合理的緩存和打包策略,這也是一種方向,【基於統計的資源打包+瀏覽器緩存】幾乎可以完敗【combo+LS】,不過這套系統的基礎設施建設成本頗高
- TBC
先說說主要會面臨的問題
1、執行速度,讀取後使用eval或創建&2、版本控制,需要自己寫一套版本控制機制。
3、localStorage是公共資源,如果你的產品域名下有很多應用共享這份資源會有風險。4、localStorage以頁面的域名劃分,而常見的靜態資源都以資源本身的域名來緩存,意味著如果你的應用有多個等價域名,它們之間的localStorage不互通,會造成緩存多份浪費。5、兼容性需要處理(不支持、隱私模式、寫滿、http/https、寫的正確性等等)=====================================
下面開始胡扯時間!如果上述2、3、4、5你覺得都不是問題的話,1一定程度上是可以量化來評估的。方案1:使用原始的緩存機制做靜態資源緩存率的收集得到比例:A%完整請求所需時間:t0304所需時間:t1
在使用瀏覽器內建機制的情況下代碼執行所需時間:t2那麼使用瀏覽器內建機制所需的時間期望就是:T1 = t0 * (1-A%) + t1 * A% + t2* 如果使用expires方式的HTTP緩存可以讓t1為0方案2:使用自定義的localStorage緩存機制
做localStorage緩存命中率測試得到比例:B%在使用localStorage讀取並執行的情況下代碼執行所需時間:t3使用localStorage的緩存命中情況比較複雜,可以稍微梳理一下:1、B%概率命中localStorage,所需時間為t32、(1-B%)概率未命中localStorage,A%概率命中HTTP緩存,所需時間為t1 + t23、(1-B%)未命中localStorage,(1-A%)概率未命中HTTP緩存,所需時間為t0 + t2
其中2和3加起來就是(1-B%) * T1對吧那麼使用localStorage緩存機制所需的時間期望就是:T2 = t3 * B% + T1 * (1-B%)* 上面忽略了localStorage緩存機制的載入器自己載入的時間、它的邏輯內部消耗和寫入localStorage等時間。
不嚴謹,不過我覺得還是有參考價值的從上面的兩個時間上看,我們預期是T2 &< T1delta = T2 - T1 = t3 * B% - T1 * B% = (t3 - T1) * B%因為B%肯定&>=0,要讓delta&<0,只要t3比T1小就行了(B%會決定收益有多大)其實說了這麼半天,也就是樓上@小爝 所言讀localstorage再eval的速度比直接載入304緩存在當成js的執行速度要慢,而且不少……呵呵。。。
因為T1當中其實還包含一定的完整載入的情況,可能結論會不至於這麼悲觀。
把T1展開分析一下,具體就不寫這裡了,結論是:
- 瀏覽器內建緩存命中率越高,對localStorage方案越不利
- 手工讀取並執行代碼vs內建機制執行代碼的速度差越多,對localStorage方案越不利
實際例子:出於某種奇特的原因,HTTP緩存命中率很低,而用戶的瀏覽器性能其實都還不錯,這種情況下方案2會比較有可能性優於方案1。
當然如果有興趣的話應該做實驗來驗證……瀏覽器都幫你緩存好了,幹嘛多此一舉緩存到LS里?
耗費成本是多少?解決了多少問題?價值幾何?維護是否方便?是否簡單易用?有足夠的開發時間么?如果你真的很閑,並且要炫酷吊炸天,搞起吧偶湊熱鬧的,碎碎念幾句。
首先不是移動端完全沒有兼容問題,問題還是有的。
1、Safari 處於隱私模式時,LS set 數據會拋異常2、不同移動端瀏覽器,對單次set數據大小是有區別的,印象(懶得換機器查PPT了)中是 3-5M 不等
3、不同移動端瀏覽器對LS總容量大小是有區別的,印象(懶得換機器查PPT了)中是 5-10M 不等(這裡指的是單子域限制)其次,IE 上可以封裝userData作為兼容。
最後,關鍵不在於LS,而在LS的控制是個問題。如:1、一個項目,LS 任何人可CURD。其中值被其他功能修改以及刪除很有可能,導致難查找的問題。2、多人寫LS,可能會寫滿,LS 總量控制如何監控,不超限3、LS 過期問題,都寫LS,過期機制是怎麼樣的,過期了優先刪除哪些騰出空間4、都沒過期的情況下,有新數據進來,刪除哪些騰出空間5、如有LS控制器封裝控制以上問題,但第三方代碼(如接入的廣告js)不使用控制器直接寫LS如何處理目前主流的網站的靜態文件都使用了緩存,比如在 nginx 中只需要加幾行配置就可以實現。只要瀏覽器不清空,某個固定鏈接的靜態文件只需要載入一次。除非文件鏈接改變後,或者頁面強制刷新,瀏覽器才會從伺服器端重新讀取靜態文件。
從這個方面看,使用 localStorage 和傳統緩存實際上是差不多的,並不能帶來較明顯的好處。另外還要考慮改造成本和兼容性等。而一些移動站點(貌似包括騰訊首頁)是使用 localStorage 來存儲 js/css 靜態文件,每次 js/css 升級時,只需要從伺服器端獲取一份包含靜態文件變動信息的文件就好了。這樣避免了靜態文件升級一次,瀏覽器就要從伺服器完整地載入一份新版本的靜態文件。
如此採取 localStorage,可以為移動用戶節省一些流量。但是後面的這種方式有一定成本和門檻。
以上拋個磚。我在實際項目中用過,後來下線了。而且不是移動應用,是pc應用。
因為最終的數據統計,你們猜是啥?
讀localstorage再eval的速度比直接載入304緩存在當成js的執行速度要慢,而且不少……呵呵。。。
那麼意義其實也就沒了……缺點還一坨。。
那些說好的,只是自己心裡認為好一些吧……還有節約流量一說。。304能費多少流量。。?搞搞清楚,別誤導人了……既然你誠心誠意的問了,那我就賤兮兮的回答一下吧= =。
優點不說了,一堆一堆的,既然你想用,總能找出來比『狂霸炫酷拽』這種理由好很多的理由來說服其他人。
我來說說缺點:
- Chrome低版本(新的開發版測試了一下似乎沒這個問題,歡迎路人童鞋繼續測試)插件插入的content script可以直接讀取修改授權站點的localstorage,代碼/數據/模板片段不安全。
- 修改後的localStorage可能造成XSS,或者業務邏輯執行失敗(看你代碼如何處理降級和回滾了)。
- 內容沒有更好的低成本計算hash以及校驗版本的方法。
- localStorage本質是SQL lite,這貨雖然能存2G,甚至4G以上的數據,但是不同瀏覽器保存上限策略不同,有的是一共保存多少有上限,有的是單獨站點(主域),有的是按子域名算的。按照域名來算的,保存上限也不一定。
- 如4描述,本地儲存爆掉,我還真見過,但是你不能隨意清理別人儲存的數據,可能會影響別人的業務(或許還是關鍵業務,請大力吐槽這種腦洞大開的行為)。
- 隱身模式,以及不支持localStorage的client太多了。
- 送你一個鏈接,來測試不同瀏覽器的儲存極限吧:soulteary/localStorage-limit-test · GitHub demo:localStorage Limit Test
- 如果出於對性能和成本的考慮,可以改進的地方太多了,可以cover更多的範圍:天下武功,唯快不破
- 盲目使用LS,還可能造成隱私/行為/癖好數據泄露(歷史問題,具體的請自己腦補)
最後吐個槽:
誰說IE不能用本地儲存的,自己封裝userData或者用Flash的Storage啊!
實在不行還有這些方案可以考慮:
- 1.Cookie(Session, Http-Only Cookie, etc…);
- 2.Local Storage (「User Data」 for ie, 「Google Gears」 for old chrome);
- 3.Flash SharedObject;
- 4.IndexedDB;
- 5.Web SQL Database;
- 6.DOM Datas;
- 7.Hash Queries;
- 8.Application Cache
摘自:瀏覽器儲存碎碎念之一:cookie
缺點是暴露你不會用 Cache-Control 響應頭。
如果你做不到字元級別的資源更新用LS性價比不高,參考騰訊的MT。
怕Ctrl+Shift+Delete..(出於好奇心剛才試了一下,FF下的clear everything都不會影響..好屌)怕用戶換瀏覽器..用來保存一些偏好設置還是不錯的。
https://mtjs.github.io/
在快速迭代版本過程中,我們有時候只修改了某個js中的幾行代碼,卻需要用戶下載整個js文件,這在重視流量的移動端顯得非常浪費,mt獨創的增強更新演算法實現了修改多少代碼就只下載修改代碼的功能,為用戶和公司節省大量流量
localstorage裡面存儲的上個版本的js內容和版本號,當本次版本號和上次版本號不一致的時候,mt拼接出增量文件url去拉取增量文件,並和上個版本的js內容合併生成新版本內容。整個方案得核心在於增量文件得計算和合併
吊炸天我現在開發的網站就是再用這種技術,最直觀的問題和我的解決方案我拿出來說一下,其中很多點 @張雲龍 都由提到: 0. 首先瀏覽器兼容毋庸置疑,這點不用多說。
- 關於eval:其中有參考 @司徒正美 的 [globalEval](js/require.config.v2.js · Gaubee / O2O_front_end_lib) 寫法,效率並不低。
- 不同域名不能共用,針對這點,我的做法是開一個lib域,專門放公共資源,因為我是基於requireJS,所以很容易在這上面做手腳(重寫[require.load](js/require.config.v2.js · Gaubee / O2O_front_end_lib)),通過postMessage來實現跨域傳輸數據來達到數據共享。而要做的就是判斷所要的資源是哪一個域的然後再通過iframe通知對應的域索要資源,而那個域的資源可以通過LS進行存儲。
- 更新問題,基於2,我們已經把一些資源通過域來進行了分類。如何更新LS中對應的資源,又能保證開發的高效,我的做法是使用git-hook,不論是國外的Github,還是國內的Git@OSC,在項目管理裡面都已一個PUSH鉤子, 用這個東西,等於是一個自動更新版本號的API,只要你提交了代碼,git伺服器就會去通知你的伺服器更新代碼與版本號。在這裡你可以用上各種優化方式來讓更新使用的流量最小化,我是圖個方便,直接清空了LS(我的資源名統一都有一個前綴,所以清空的是帶有這個前綴的key-value),然後再需要什麼就下載什麼,你可以用MD5值來進一步的做優化。
我聽說用 localStorage 存靜態資源最有價值的例子是亞馬遜。他們因為經常做 A/B test,一個頁面中經常跑很多個 test,每個用戶需要的靜態資源都不一樣。於是他們就先內聯所有還在 experiment 的資源,然後存到 localStorage 里,伺服器如果判斷到用戶已經緩存了靜態資源到 localStorage ,下次輸出 HTML 的時候就不會內聯資源了。
對於題主的需求,更應該關注的技術是application cache,緩存靜態資源完全沒有問題。如果瀏覽器支持的話,可以做到離線訪問;如果瀏覽器不支持也完全沒有副作用。
參考: Application Cache區分一下application cache和localstorage的使用場景,application cache只能緩存請求,所以js/css這種靜態資源很適合用application cache緩存;localstorage只能緩存內容,所以更適合存放數據。如果非要把js和css放到localstorage中,感覺不僅讀寫麻煩,安全隱患大,而且對開發人員來說,根本是在以一種非常不直觀的形式做一些原本很簡單的事情。在當前的網路環境下,PC上使用的意義不大,因為304的時間足夠快了,而且配合設置好資源的失效時間,除非是用戶的主動刷新,不然304的過程都是可以不用有的,瀏覽器不用去check一次資源是否失效,直接可以本地載入使用。
---------------------但是,我們的項目在用,而且整個資源載入架構是基於LS的---------------------
原因:1.我們是PC上的單頁應用,除了主框架,其他都是由js來生成的;2.我們是複雜的企業應用,大功能劃分有10來個,每個大功能中可能有10來個小功能,而且我可以預見功能還會增加;3.我們現代開發:js模塊化,載入combo化,js文件+css文件目前有接近200個;4.雖然是企業應用,但是迭代更新快,這意味著我們緩存很快就失效了,比較常見的是某個模塊的小小修改導致整個 A+B+C+D+E+F.js URL的緩存失效,需要重新來一次;5.雖然是企業應用,但是我們不需要關心IE6 IE7;6.我們不需要考慮SEO。因為項目如此,所以我們的整個載入架構是基於seajs + LS的,而且不僅僅是每個js文件,我們根據大業務功能,把css文件也加以劃分,構建的時候把這些css -&> css.js,同時走到LS存儲中去。
於是可以很輕鬆的做到 : 哪裡變化,載入哪裡,保證了大部分的緩存有效。但是主框架的css文件還有seajs+seajsplugin這種,長時間不需要修改的文件不走LS存儲,正常載入,加上max-age的正確設置,保證了首屏的快速打開,不要白屏太久。回到問題,舉個例子:
你需要去家旁邊的超市買點東西,開著你的SUV,一定不會幫你節約時間,你甚至還需要去拿車,找地方停車,可是如果你要去10公里外的宜家採購,那開車一定是很方便的。我的意思是:需不需要用LS來載入資源,要因地制宜,結合你的項目來做分析,不會有一個非常好的答案是好還是不好,大家不用可能只是不適合大家的項目而已,我用,是因為適合我的項目。美團和騰訊在移動端都在用這個方案,主要是為了省流量吧,當然還可以來做離線應用,缺點前面回答很詳細了。
可以用,只是大部分app開發的同學還沒想到app上的瀏覽器應該都做了這個事情
pc端沒有大規模應用起來因為pc端不具備使用的條件(ie兼容,ls支持),或根本不需要(瀏覽器緩存,cdn,網速)。在pc端使用ls存儲的話,工程師 維護 更新(特別是更新,如果更新頻繁,而且更新不是增量更新的話,確實不如不用) ls存儲 花費的精力也會更多,不值得另,遇到的一個bug,ie10 or ie11可設置瀏覽器安全級別(好像),即便是讀取ls加了異常,也還是會報錯,因為讀ls的許可權或根本就訪問不到ls。樓上還有人說,可以做離線應用,說實話,對於資源嚴重依賴互聯網的產品來說,是雞肋。--------------可愛的分割線---------------------即便是這樣,ls也還是可以用的,瀏覽器分級支持嘛,好的瀏覽器不就應該享受更好的體驗么~~mt可以做到ls的增量更新,且可做到不需要304請求驗證緩存有效性~~
最佳方案應該是增量更新方案,前面有提到MT,現在多數的React-Native對jsbundle文件的更新都採用了這種增量更新的思想,實現最小量的下載。可以研究研究,增量diff演算法開源的也很常見了。
localStorage是一個js對象,而瀏覽器的緩存技術是基於瀏覽器的。你說是js快還是瀏覽器快。
推薦閱讀:
※HTML5 或者JS可以獲取移動設備信息嗎?或者其他非開發語言的方法?
※html5、jquery怎樣實現在回到歷史頁面時完全保留之前離開這個頁面時的狀態?
※有哪些不用編寫代碼就能輕鬆製作生成HTML5頁面的工具?
※HTML5 會不會使 Linux 比 Windows 更受歡迎?
※jquery網頁載入進度條思路?
TAG:互聯網 | 前端開發 | JavaScript | HTML5 | 前端性能優化 |