瀏覽器儲存方案
前言
瀏覽器有多種儲存資料的辦法,包括Cookie,web storage(localStorage和sessionStorage),indexedDB。它們的主要目的都是快速訪問資料而不需要建立請求去伺服器獲取。它們有著不一樣的大小,生命週期和作用域,所以需要在合適的場景選擇合適的資料儲存辦法。
cookie
cookie是為了解決HTTP協議的無狀態而引入的技術。HTTP Cookie(也叫Web Cookie或瀏覽器Cookie)是伺服器傳送到使用者瀏覽器並儲存在本地的一小塊資料,它會在瀏覽器下次向同一伺服器再發起請求時被攜帶併發送到伺服器上。通常,它用於告知服務端兩個請求是否來自同一瀏覽器,如保持使用者的登入狀態。Cookie使基於無狀態的HTTP協議記錄穩定的狀態資訊成為了可能。
Cookie主要用於以下三個方面:
- 會話狀態管理(如使用者登入狀態、購物車、遊戲分數或其它需要記錄的資訊)
- 個性化設定(如使用者自定義設定、主題等)
- 瀏覽器行為跟蹤(如跟蹤分析使用者行為等)
Cookie曾一度用於客戶端資料的儲存,因當時並沒有其它合適的儲存辦法而作為唯一的儲存手段,但現在隨著現代瀏覽器開始支援各種各樣的儲存方式,Cookie漸漸被淘汰。由於伺服器指定Cookie後,瀏覽器的每次請求都會攜帶Cookie資料,會帶來額外的效能開銷(尤其是在移動環境下)。新的瀏覽器API已經允許開發者直接將資料儲存到本地,如使用 Web storage API (本地儲存和會話儲存)或 IndexedDB 。
建立Cookie
當伺服器收到HTTP請求時,伺服器可以在響應頭裡面新增一個Set-Cookie選項。瀏覽器收到響應後通常會儲存下Cookie,之後對該伺服器每一次請求中都通過Cookie請求頭部將Cookie資訊傳送給伺服器。另外,Cookie的過期時間、域、路徑、有效期、適用站點都可以根據需要來指定。
伺服器使用Set-Cookie響應頭部向用戶代理(一般是瀏覽器)傳送Cookie資訊,比如Set-Cookie: status=enable; expires=Tue, 05 Jul 2011 07:26:31 GMT; path=/; domain=clloz.com;
,伺服器通過該頭部告知客戶端儲存Cookie資訊。
如果不對Cookie設定expires
則該Cookie被認為是會話期Cookie,會話期Cookie是最簡單的Cookie:瀏覽器關閉之後它會被自動刪除,也就是說它僅在會話期內有效。會話期Cookie不需要指定過期時間(Expires)或者有效期(Max-Age)。需要注意的是,有些瀏覽器提供了會話恢復功能,這種情況下即使關閉了瀏覽器,會話期Cookie也會被保留下來,就好像瀏覽器從來沒有關閉一樣。而設定了expires
的Cookie則被認為是永續性的Cookie,規定了Cookie的過期時間,而max-age
引數則規定了Cookie過期的相對時間。
Cookie的安全
標記為 Secure 的Cookie只應通過被HTTPS協議加密過的請求傳送給服務端。但即便設定了 Secure 標記,敏感資訊也不應該通過Cookie傳輸,因為Cookie有其固有的不安全性,Secure 標記也無法提供確實的安全保障。從 Chrome 52 和 Firefox 52 開始,不安全的站點(http:)無法使用Cookie的 Secure 標記。
為避免跨域指令碼 (XSS) 攻擊,通過JavaScript的 Document.cookie API無法訪問帶有 HttpOnly 標記的Cookie,它們只應該傳送給服務端。如果包含服務端 Session 資訊的 Cookie 不想被客戶端 JavaScript 指令碼呼叫,那麼就應該為其設定 HttpOnly 標記。
Cookie的作用域
Domain 和 Path 標識定義了Cookie的作用域:即Cookie應該傳送給哪些URL。
Domain 標識指定了哪些主機可以接受Cookie。如果不指定,預設為當前文件的主機(不包含子域名)。如果指定了Domain,則一般包含子域名。
例如,如果設定Domain=mozilla.org
,則Cookie也包含在子域名中(如developer.mozilla.org)。
Path 標識指定了主機下的哪些路徑可以接受Cookie(該URL路徑必須存在於請求URL中)。以字元%x2F ("/")
作為路徑分隔符,子路徑也會被匹配。
例如,設定Path=/docs
,則以下地址都會匹配:/docs
,/docs/Web/
,/docs/Web/HTTP
JS操作Cookie
通過Document.cookie
屬性可建立新的Cookie,也可通過該屬性訪問非HttpOnly標記的Cookie。由於document.cookie
屬性只給出了cookie
字串,而沒有給出操作cookie的方法,所以cookie的讀寫刪需要自己寫方法封裝。
JS設定cookie:document.cookie="age=12; expires=Thu, 26 Feb 2116 11:50:25 GMT; domain=sankuai.com; path=/";
設定多個cookie:
document.cookie = "name=Jonh"; document.cookie = "age=12"; document.cookie = "class=111";
要想修改一個cookie,只需要重新賦值就行,舊的值會被新的值覆蓋。但要注意一點,在設定新cookie時,path/domain這幾個選項一定要舊cookie 保持一樣。否則不會修改舊值,而是添加了一個新的 cookie。
刪除一個cookie 也挺簡單,也是重新賦值,只要將這個新cookie的expires 選項設定為一個過去的時間點就行了。但同樣要注意,path/domain/這幾個選項一定要舊cookie 保持一樣。
cookie其實是個字串,但這個字串中逗號、分號、空格被當做了特殊符號。所以當cookie的 key 和 value 中含有這3個特殊字元時,需要對其進行額外編碼,一般會用escape
進行編碼,讀取時用unescape
進行解碼;當然也可以用encodeURIComponent/decodeURIComponent
或者encodeURI/decodeURI
。
Web Storage
Web Storage 包含如下兩種機制:
1. sessionStorage:為每一個給定的源(given origin)維持一個獨立的儲存區域,該儲存區域在頁面會話期間可用(即只要瀏覽器處於開啟狀態,包括頁面重新載入和恢復)。
2. localStorage:同樣的功能,但是在瀏覽器關閉,然後重新開啟後資料仍然存在。
這兩種機制是通過Window.sessionStorage
和Window.localStorage
屬性使用(更確切的說,在支援的瀏覽器中 Window 物件實現了WindowLocalStorage
和WindowSessionStorage
物件並掛在其localStorage
和sessionStorage
屬性下)—— 呼叫其中任一物件會建立Storage
物件,通過Storage
物件,可以設定、獲取和移除資料項。對於每個源(origin)sessionStorage 和 localStorage 使用不同的 Storage 物件——獨立執行和控制。
sessionStorage
sessionStorage 屬性允許你訪問一個 session Storage 物件。它與 localStorage 相似,不同之處在於 localStorage 裡面儲存的資料沒有過期時間設定,而儲存在 sessionStorage 裡面的資料在頁面會話結束時會被清除。頁面會話在瀏覽器開啟期間一直保持,並且重新載入或恢復頁面仍會保持原來的頁面會話。在新標籤或視窗開啟一個頁面時會在頂級瀏覽上下文中初始化一個新的會話,這點和 session cookies 的執行方式不同。
// 儲存資料到 sessionStorage sessionStorage.setItem('key', 'value'); // 從 sessionStorage 獲取資料 let data = sessionStorage.getItem('key'); // 從 sessionStorage 刪除儲存的資料 sessionStorage.removeItem('key'); // 從 sessionStorage 刪除所有儲存的資料 sessionStorage.clear();
localStorage
只讀的localStorage 屬性允許你訪問一個Document 源(origin)的物件 Storage;其儲存的資料能在跨瀏覽器會話保留。localStorage 類似 sessionStorage,但其區別在於:儲存在 localStorage 的資料可以長期保留;而當頁面會話結束——也就是說,當頁面被關閉時,儲存在 sessionStorage 的資料會被清除 。localStorage 中的鍵值對總是以字串的形式儲存。
//增加了一個數據專案。 localStorage.setItem('myCat', 'Tom'); //讀取 localStorage 項 let cat = localStorage.getItem('myCat'); //移除 localStorage 項 localStorage.removeItem('myCat'); // 移除所有 localStorage.clear();