三種本地存儲方式
cookie
前言
網絡早期最大的問題之一是如何管理狀態。簡而言之,服務器無法知道兩個請求是否來自同一個瀏覽器。當時最簡單的方法是在請求時,在頁面中插入一些參數,并在下一個請求中傳回參數。這需要使用包含參數的隱藏的表單,或者作為
URL
參數的一部分傳遞。這兩個解決方案都手動操作,容易出錯。cookie
出現來解決這個問題。
作用
cookie
是純文本,沒有可執行代碼。存儲數據,當用戶訪問了某個網站(網頁)的時候,我們就可以通過cookie
來向訪問者電腦上存儲數據,或者某些網站為了辨別用戶身份、進行session
跟蹤而儲存在用戶本地終端上的數據(通常經過加密)
如何工作
當網頁要發
http
請求時,瀏覽器會先檢查是否有相應的cookie
,有則自動添加在request header
中的cookie
字段中。這些是瀏覽器自動幫我們做的,而且每一次http
請求瀏覽器都會自動幫我們做。這個特點很重要,因為這關系到“什么樣的數據適合存儲在cookie中”。
存儲在
cookie
中的數據,每次都會被瀏覽器自動放在http
請求中,如果這些數據并不是每個請求都需要發給服務端的數據,瀏覽器這設置自動處理無疑增加了網絡開銷;但如果這些數據是每個請求都需要發給服務端的數據(比如身份認證信息),瀏覽器這設置自動處理就大大免去了重復添加操作。所以對于那種設置“每次請求都要攜帶的信息(最典型的就是身份認證信息)”就特別適合放在cookie
中,其他類型的數據就不適合了。
特征
1. 不同的瀏覽器存放的cookie
位置不一樣,也是不能通用的。
2. cookie
的存儲是以域名形式進行區分的,不同的域下存儲的cookie
是獨立的。
3. 我們可以設置cookie
生效的域(當前設置cookie
所在域的子域),也就是說,我們能夠操作的cookie
是當前域以及當前域下的所有子域
4. 一個域名下存放的cookie
的個數是有限制的,不同的瀏覽器存放的個數不一樣,一般為20個。
5.每個cookie
存放的內容大小也是有限制的,不同的瀏覽器存放大小不一樣,一般為4KB。
6. cookie
也可以設置過期的時間,默認是會話結束的時候,當時間到期自動銷毀
cookie值既可以設置,也可以讀取。
設置
客戶端設置
document.cookie = '名字=值';
document.cookie = 'username=cfangxu;domain=baike.baidu.com' 并且設置了生效域
注意: 客戶端可以設置cookie 的下列選項:expires、domain、path、secure(有條件:只有在https協議的網頁中,客戶端設置secure類型的 cookie 才能成功),但無法設置HttpOnly選項。
服務器端設置
不管你是請求一個資源文件(如 html/js/css/圖片),還是發送一個
ajax
請求,服務端都會返回response
。而response header
中有一項叫set-cookie
,是服務端專門用來設置cookie
的。
Set-Cookie 消息頭是一個字符串,其格式如下(中括號中的部分是可選的):
Set-Cookie: value[; expires=date][; domain=domain][; path=path][; secure]
注意: 一個set-Cookie字段只能設置一個cookie,當你要想設置多個 cookie,需要添加同樣多的set-Cookie字段。
- 服務端可以設置cookie 的所有選項: expires、domain、path、secure、HttpOnly
- 通過 Set-Cookie 指定的這些可選項只會在瀏覽器端使用,而不會被發送至服務器端。
讀取
我們通過
document.cookie
來獲取當前網站下的cookie
的時候,得到的字符串形式的值,它包含了當前網站下所有的cookie
(為避免跨域腳本(xss)攻擊,這個方法只能獲取非 HttpOnly 類型的cookie)。它會把所有的cookie
通過一個分號+空格的形式串聯起來,例如username=chenfangxu; job=coding
修改 cookie
要想修改一個
cookie
,只需要重新賦值就行,舊的值會被新的值覆蓋。但要注意一點,在設置新cookie
時,path/domain
這幾個選項一定要舊cookie
保持一樣。否則不會修改舊值,而是添加了一個新的cookie
。
刪除
把要刪除的
cookie
的過期時間設置成已過去的時間path/domain/
這幾個選項一定要舊cookie
保持一樣。
注意
如果只設置一個值,那么算
cookie
中的value
設置的兩個cookie,key
值如果設置的相同,下面的也會把上面的覆蓋。
cookie的屬性(可選項)
過期時間
如果我們想長時間存放一個
cookie
。需要在設置這個cookie
的時候同時給他設置一個過期的時間。如果不設置,cookie
默認是臨時存儲的,當瀏覽器關閉進程的時候自動銷毀
注意:document.cookie = '名稱=值;expires=' + GMT(格林威治時間)格式的日期型字符串;
一般設置天數:new Date().setDate( oDate.getDate() + 5 ); 比當前時間多5天
一個設置cookie時效性的例子
function setCookie(c_name, value, expiredays){
var exdate=new Date();
exdate.setDate(exdate.getDate() + expiredays);
document.cookie=c_name+ "=" + escape(value) + ((expiredays==null) ? "" : ";
expires="+exdate.toGMTString())
}
使用方法:setCookie('username','cfangxu',30)
- expires 是 http/1.0協議中的選項,在新的http/1.1協議中expires已經由 max-age 選項代替,兩者的作用都是限制cookie 的有效時間。expires的值是一個時間點(cookie失效時刻= expires),而max-age 的值是一個以秒為單位時間段(cookie失效時刻= 創建時刻+ max-age)。
- 另外,max-age 的默認值是 -1(即有效期為 session );max-age有三種可能值:負數、0、正數。
- 負數:有效期session;
- 0:刪除cookie;
- 正數:有效期為創建時刻+ max-age
cookie的域概念(domain選項)
domain
指定了cookie
將要被發送至哪個或哪些域中。默認情況下,domain
會被設置為創建該cookie
的頁面所在的域名,所以當給相同域名發送請求時該cookie
會被發送至服務器。
瀏覽器會把domain
的值與請求的域名做一個尾部比較(即從字符串的尾部開始比較),并將匹配的 cookie
發送至服務器。
客戶端設置
document.cookie = "username=cfangxu;path=/;domain=qq.com"
- 如上:
“www.qq.com"
與"sports.qq.com"
公用一個關聯的域名"qq.com"
,我們如果想讓"sports.qq.com"
下的cookie
被"www.qq.com"
訪問,我們就需要用到cookie
的domain
屬性,并且需要把path
屬性設置為 "/"。
服務端設置
Set-Cookie: username=cfangxu;path=/;domain=qq.com
注:一定的是同域之間的訪問,不能把domain的值設置成非主域的域名。
cookie的路徑概念(path選項)
cookie
一般都是由于用戶訪問頁面而被創建的,可是并不是只有在創建cookie
的頁面才可以訪問這個cookie
。
因為安全方面的考慮,默認情況下,只有與創建cookie
的頁面在同一個目錄或子目錄下的網頁才可以訪問。
即path
屬性可以為服務器特定文檔指定cookie
,這個屬性設置的url
且帶有這個前綴的url
路徑都是有效的。
客戶端設置
最常用的例子就是讓
cookie
在根目錄下,這樣不管是哪個子頁面創建的cookie
,所有的頁面都可以訪問到了。
document.cookie = "username=cfangxu; path=/"
服務端設置
Set-Cookie:name=cfangxu; path=/blog
- 如上設置:path 選項值會與 /blog,/blogrool 等等相匹配;任何以 /blog 開頭的選項都是合法的。需要注意的是,只有在 domain 選項核實完畢之后才會對 path 屬性進行比較。path 屬性的默認值是發送 Set-Cookie 消息頭所對應的 URL 中的 path 部分。
domain和path總結:
domain
是域名,path
是路徑,兩者加起來就構成了URL
,domain
和path
一起來限制cookie
能被哪些URL
訪問。
所以domain
和path
2個選項共同決定了cookie
何時被瀏覽器自動添加到請求頭部中發送出去。如果沒有設置這兩個選項,則會使用默認值。domain
的默認值為設置該cookie
的網頁所在的域名,path
默認值為設置該cookie
的網頁所在的目錄。
cookie的安全性(secure選項)
通常
cookie
信息都是使用HTTP
連接傳遞數據,這種傳遞方式很容易被查看,所以cookie
存儲的信息容易被竊取。假如cookie
中所傳遞的內容比較重要,那么就要求使用加密的數據傳輸。
secure
選項用來設置cookie
只在確保安全的請求中才會發送。當請求是HTTPS
或者其他安全協議時,包含secure
選項的cookie
才能被發送至服務器。
document.cookie = "username=cfangxu; secure"
- 把cookie設置為secure,只保證 cookie 與服務器之間的數據傳輸過程加密,而保存在本地的 cookie文件并不加密。就算設置了secure 屬性也并不代表他人不能看到你機器本地保存的 cookie 信息。機密且敏感的信息絕不應該在 cookie 中存儲或傳輸,因為 cookie 的整個機制原本都是不安全的
注意:如果想在客戶端即網頁中通過 js 去設置secure類型的 cookie,必須保證網頁是https協議的。在http協議的網頁中是無法設置secure類型cookie的。
httpOnly
這個選項用來設置
cookie
是否能通過js
去訪問。默認情況下,cookie
不會帶httpOnly
選項(即為空),所以默認情況下,客戶端是可以通過js
代碼去訪問(包括讀取、修改、刪除等)這個cookie
的。當cookie
帶httpOnly
選項時,客戶端則無法通過js
代碼去訪問(包括讀取、修改、刪除等)這個cookie。
在客戶端是不能通過js代碼去設置一個httpOnly類型的cookie的,這種類型的cookie只能通過服務端來設置。
cookie的編碼
cookie
其實是個字符串,但這個字符串中等號、分號、空格被當做了特殊符號。所以當cookie
的key
和value
中含有這3個特殊字符時,需要對其進行額外編碼,一般會用escape
進行編碼,讀取時用unescape
進行解碼;當然也可以用encodeURIComponent/decodeURIComponent
或者encodeURI/decodeURI
,查看關于編碼的介紹
第三方cookie
通常
cookie
的域和瀏覽器地址的域匹配,這被稱為第一方cookie
。那么第三方cookie
就是cookie
的域和地址欄中的域不匹配,這種cookie
通常被用在第三方廣告網站。為了跟蹤用戶的瀏覽記錄,并且根據收集的用戶的瀏覽習慣,給用戶推送相關的廣告。
關于第三方cookie
和cookie
的安全問題可以查看https://mp.weixin.qq.com/s/oOGIuJCplPVW3BuIx9tNQg
localStorage(本地存儲)
HTML5
新方法,不過IE8
及以上瀏覽器都兼容。
特點
- 生命周期:持久化的本地存儲,除非主動刪除數據,否則數據是永遠不會過期的。
- 存儲的信息在同一域中是共享的。
- 當本頁操作(新增、修改、刪除)了localStorage的時候,本頁面不會觸發storage事件,但是別的頁面會觸發storage事件。
- 大小:據說是5M(跟瀏覽器廠商有關系)
- 在非IE下的瀏覽中可以本地打開。IE瀏覽器要在服務器中打開。
- localStorage本質上是對字符串的讀取,如果存儲內容多的話會消耗內存空間,會導致頁面變卡
- localStorage受同源策略的限制
設置
localStorage.setItem('username','cfangxu');
獲取
localStorage.getItem('username')
也可以獲取鍵名
localStorage.key(0) #獲取第一個鍵名
刪除
localStorage.removeItem('username')
也可以一次性清除所有存儲
localStorage.clear()
storage事件
當
storage
發生改變的時候觸發。
注意:
- 當前頁面對storage的操作會觸發其他頁面的storage事件
- 事件的回調函數中有一個參數event,是一個StorageEvent對象,提供了一些實用的屬性
如下表:
Property | Type | Description |
---|---|---|
key | String | The named key that was added, removed, or moddified |
oldValue | Any | The previous value(now overwritten), or null if a new item was added |
newValue | Any | The new value, or null if an item was added |
url/uri | String | The page that called the method that triggered this change |
sessionStorage
其實跟
localStorage
差不多,也是本地存儲,會話本地存儲
特點:
用于本地存儲一個會話(session)中的數據,這些數據只有在同一個會話中的頁面才能訪問并且當會話結束后數據也隨之銷毀。因此
sessionStorage
不是一種持久化的本地存儲,僅僅是會話級別的存儲。也就是說只要這個瀏覽器窗口沒有關閉,即使刷新頁面或進入同源另一頁面,數據仍然存在。關閉窗口后,sessionStorage
即被銷毀,或者在新窗口打開同源的另一個頁面,sessionStorage
也是沒有的。
cookie、localStorage、sessionStorage區別
相同: 在本地(瀏覽器端)存儲數據
不同:
- localStorage、sessionStorage
- localStorage只要在相同的協議、相同的主機名、相同的端口下,就能讀取/修改到同一份localStorage數據。
- sessionStorage比localStorage更嚴苛一點,除了協議、主機名、端口外,還要求在同一窗口(也就是瀏覽器的標簽頁)下。
- localStorage是永久存儲,除非手動刪除。
- sessionStorage當會話結束(當前頁面關閉的時候,自動銷毀)
- cookie的數據會在每一次發送http請求的時候,同時發送給服務器而localStorage、sessionStorage不會。
擴展其他的前端存儲方式(不常用)
web SQL database
先說個會被取代的,為什么會被取代,主要有以下幾個原因:
- W3C舍棄
Web SQL database
草案,而且是在2010年年底,規范不支持了,瀏覽器廠商已經支持的就支持了,沒有支持的也不打算支持了,比如IE和Firefox。 - 為什么要舍棄?因為
Web SQL database
本質上是一個關系型數據庫,后端可能熟悉,但是前端就有很多不熟悉了,雖然SQL的簡單操作不難,但是也得需要學習。 - SQL熟悉后,真實操作中還得把你要存儲的東西,比如對象,轉成SQL語句,也挺麻煩的。
indexedDB
來自MDN的解釋:
indexedDB
是一種低級API
,用于客戶端存儲大量結構化數據(包括, 文件/ blobs)。該API
使用索引來實現對該數據的高性能搜索。雖然Web Storage
對于存儲較少量的數據很有用,但對于存儲更大量的結構化數據來說,這種方法不太有用。IndexedDB
提供了一個解決方案。
- 所以,
IndexedDB
API是強大的,但對于簡單的情況可能看起來太復雜了,所以要看你的業務場景來選擇到底是用還是不用。 -
indexedDB
是一個基于JavaScript的面向對象的數據庫。IndexedDB
允許你存儲和檢索用鍵索引的對象;
IndexedDB 鼓勵使用的基本模式如下所示:
- 打開數據庫并且開始一個事務。
- 創建一個 object store。
- 構建一個請求來執行一些數據庫操作,像增加或提取數據等。
- 通過監聽正確類型的 DOM 事件以等待操作完成。
- 在操作結果上進行一些操作(可以在 request 對象中找到)
1、首先打開indexedDB數據庫
語法:
window.indexedDB.open(dbName, version)
var db;
// 打開數據庫,open還有第二個參數版本號
var request = window.indexedDB.open('myTestDatabase');
// 數據庫打開成功后
request.onsuccess = function (event) {
// 存儲數據結果,后面所有的數據庫操作都離不開它。
db = request.result;
}
request.onerror = function (event) {
alert("Why didn't you allow my web app to use IndexedDB?!");
}
// 數據庫首次創建版本,或者window.indexedDB.open傳遞的新版本(版本數值要比現在的高)
request.onupgradeneeded = function (event) {
}
-
onupgradeneeded事件: 更新數據庫的
schema
,也就是創建或者刪除對象存儲空間,這個事件將會作為一個允許你處理對象存儲空間的versionchange
事務的一部分被調用。在數據庫第一次被打開時或者當指定的版本號高于當前被持久化的數據庫的版本號時,這個versionchange
事務將被創建。onupgradeneeded
是我們唯一可以修改數據庫結構的地方。在這里面,我們可以創建和刪除對象存儲空間以及構建和刪除索引。
2、構建數據庫
IndexedDB
使用對象存儲空間而不是表,并且一個單獨的數據庫可以包含任意數量的對象存儲空間。每當一個值被存儲進一個對象存儲空間時,它會被和一個鍵相關聯。
// 數據庫首次創建版本,或者window.indexedDB.open傳遞的新版本(版本數值要比現在的高)
request.onupgradeneeded = function (event) {
//之前咱們不是在success中得到了db了么,為什么還要在這獲取,
//因為在當前事件函數執行后才會去執行success事件
var db = event.target.result;
// 創建一個對象存儲空間,keyPath是id,keyGenerator是自增的
var objectStore = db.createObjectStore('testItem',{keyPath: 'id',autoIncrement: true});
// 創建一個索引來通過id搜索,id是自增的,不會有重復,所以可以用唯一索引
objectStore.createIndex('id','id',{unique: true})
objectStore.createIndex('name','name');
objectStore.createIndex('age','age');
//添加一條信息道數據庫中
objectStore.add({name: 'cfangxu', age: '27'});
}
注意: 執行完后,在調試工具欄Application的indexedDB中也看不到,你得右鍵刷新一下。
創建索引的語法:
objectStore.createIndex(indexName, keyPath, objectParameters)
indexName:創建的索引名稱,可以使用空名稱作為索引。
keyPath:索引使用的關鍵路徑,可以使用空的keyPath, 或者keyPath傳為數組keyPath也是可以的。
objectParameters:可選參數。常用參數之一是unique,表示該字段值是否唯一,不能重復。例如,本demo中id是不能重復的,于是有設置:
3、添加數據
上面的代碼建好了字段,并且添加了一條數據,但是我們如果想在
onupgradeneeded
事件外面操作,接下來的步驟了。
由于數據庫的操作都是基于事務(transaction)來進行,于是,無論是添加編輯還是刪除數據庫,我們都要先建立一個事務(transaction),然后才能繼續下面的操作。
語法:
var transaction = db.transaction(dbName, "readwrite");
- 第一個參數是事務希望跨越的對象存儲空間的列表,可以是數組或者字符串。如果你希望事務能夠跨越所有的對象存儲空間你可以傳入一個空數組。如果你沒有為第二個參數指定任何內容,你得到的是只讀事務。因為這里我們是想要寫入所以我們需要傳入 "readwrite" 標識。
var timer = setInterval(function () {
if(db) {
clearInterval(timer);
// 新建一個事務
var transaction = db.transaction(['testItem'], 'readwrite');
// 打開一個存儲對象
var objectStore = transaction.objectStore('testItem');
// 添加數據到對象中
objectStore.add({ name: 'xiaoming', age: '12' });
objectStore.add({ name: 'xiaolong', age: '20' });
}
},100)
為什么要用一個間隔定時器? 因為這是一個demo,正常的是要有操作才能進行數據庫的寫入,在我們的demo中,js執行到transaction會比indexedDB的onsuccess事件回調快,導致會拿到db為undefined,所以寫了個間隔定時器等它一會。
4、獲取數據
var transaction = db.transaction(['testItem'], 'readwrite');
var objectStore = transaction.objectStore('testItem');
var getRquest = objectStore.get(1);
getRquest.onsuccess = function (event) {
console.log(getRquest.result);
}
//輸出:{name: "cfangxu", age: "27", id: 1}
5、修改數據
var transaction = db.transaction(['testItem'], 'readwrite');
var objectStore = transaction.objectStore('testItem');
var getRquest = objectStore.put({ name: 'chenfangxu', age: '27', id:1 });
// 修改了id為1的那條數據
6、刪除數據
var transaction = db.transaction(['testItem'], 'readwrite');
var objectStore = transaction.objectStore('testItem');
var getRquest = objectStore.delete(1);
// 刪除了id為1的那條數據
- 上面的例子執行完后,一定一定要右鍵刷新indexedDB,它自己是不會變的。
- 關于數據庫的名詞解釋和indexedDB的游標介紹參閱:HTML5 indexedDB前端本地存儲數據庫實例教程