cookie
前言
網(wǎng)絡(luò)早期最大的問題之一是如何管理狀態(tài)。簡(jiǎn)而言之,服務(wù)器無法知道兩個(gè)請(qǐng)求是否來自同一個(gè)瀏覽器。當(dāng)時(shí)最簡(jiǎn)單的方法是在請(qǐng)求時(shí),在頁面中插入一些參數(shù),并在下一個(gè)請(qǐng)求中傳回參數(shù)。這需要使用包含參數(shù)的隱藏的表單,或者作為URL參數(shù)的一部分傳遞。這兩個(gè)解決方案都手動(dòng)操作,容易出錯(cuò)。cookie出現(xiàn)來解決這個(gè)問題。
作用
cookie是純文本,沒有可執(zhí)行代碼。存儲(chǔ)數(shù)據(jù),當(dāng)用戶訪問了某個(gè)網(wǎng)站(網(wǎng)頁)的時(shí)候,我們就可以通過cookie來向訪問者電腦上存儲(chǔ)數(shù)據(jù),或者某些網(wǎng)站為了辨別用戶身份、進(jìn)行session跟蹤而儲(chǔ)存在用戶本地終端上的數(shù)據(jù)(通常經(jīng)過加密)
如何工作
當(dāng)網(wǎng)頁要發(fā)http請(qǐng)求時(shí),瀏覽器會(huì)先檢查是否有相應(yīng)的cookie,有則自動(dòng)添加在request header中的cookie字段中。這些是瀏覽器自動(dòng)幫我們做的,而且每一次http請(qǐng)求瀏覽器都會(huì)自動(dòng)幫我們做。這個(gè)特點(diǎn)很重要,因?yàn)檫@關(guān)系到“什么樣的數(shù)據(jù)適合存儲(chǔ)在cookie中”。
存儲(chǔ)在cookie中的數(shù)據(jù),每次都會(huì)被瀏覽器自動(dòng)放在http請(qǐng)求中,如果這些數(shù)據(jù)并不是每個(gè)請(qǐng)求都需要發(fā)給服務(wù)端的數(shù)據(jù),瀏覽器這設(shè)置自動(dòng)處理無疑增加了網(wǎng)絡(luò)開銷;但如果這些數(shù)據(jù)是每個(gè)請(qǐng)求都需要發(fā)給服務(wù)端的數(shù)據(jù)(比如身份認(rèn)證信息),瀏覽器這設(shè)置自動(dòng)處理就大大免去了重復(fù)添加操作。所以對(duì)于那種設(shè)置“每次請(qǐng)求都要攜帶的信息(最典型的就是身份認(rèn)證信息)”就特別適合放在cookie中,其他類型的數(shù)據(jù)就不適合了。
特征
- 不同的瀏覽器存放的cookie位置不一樣,也是不能通用的。
- cookie的存儲(chǔ)是以域名形式進(jìn)行區(qū)分的,不同的域下存儲(chǔ)的cookie是獨(dú)立的。
- 我們可以設(shè)置cookie生效的域(當(dāng)前設(shè)置cookie所在域的子域),也就是說,我們能夠操作的cookie是當(dāng)前域以及當(dāng)前域下的所有子域
- 一個(gè)域名下存放的cookie的個(gè)數(shù)是有限制的,不同的瀏覽器存放的個(gè)數(shù)不一樣,一般為20個(gè)。
- 每個(gè)cookie存放的內(nèi)容大小也是有限制的,不同的瀏覽器存放大小不一樣,一般為4KB。
- cookie也可以設(shè)置過期的時(shí)間,默認(rèn)是會(huì)話結(jié)束的時(shí)候,當(dāng)時(shí)間到期自動(dòng)銷毀
cookie值既可以設(shè)置,也可以讀取。
設(shè)置
客戶端設(shè)置
<pre class="hljs language-ini" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">document.cookie = '名字=值';
document.cookie = 'username=cfangxu;domain=baike.baidu.com' 并且設(shè)置了生效域</pre>
注意: 客戶端可以設(shè)置cookie 的下列選項(xiàng):expires、domain、path、secure(有條件:只有在https協(xié)議的網(wǎng)頁中,客戶端設(shè)置secure類型的 cookie 才能成功),但無法設(shè)置HttpOnly選項(xiàng)。
服務(wù)器端設(shè)置
不管你是請(qǐng)求一個(gè)資源文件(如 html/js/css/圖片),還是發(fā)送一個(gè)ajax請(qǐng)求,服務(wù)端都會(huì)返回response。而response header中有一項(xiàng)叫set-cookie,是服務(wù)端專門用來設(shè)置cookie的。
<pre class="hljs language-pgsql" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Set-Cookie 消息頭是一個(gè)字符串,其格式如下(中括號(hào)中的部分是可選的):
Set-Cookie: value[; expires=date][; domain=domain][; path=path][; secure]</pre>
注意: 一個(gè)set-Cookie字段只能設(shè)置一個(gè)cookie,當(dāng)你要想設(shè)置多個(gè) cookie,需要添加同樣多的set-Cookie字段。
服務(wù)端可以設(shè)置cookie 的所有選項(xiàng):expires、domain、path、secure、HttpOnly
通過 Set-Cookie 指定的這些可選項(xiàng)只會(huì)在瀏覽器端使用,而不會(huì)被發(fā)送至服務(wù)器端。
讀取
我們通過document.cookie來獲取當(dāng)前網(wǎng)站下的cookie的時(shí)候,得到的字符串形式的值,它包含了當(dāng)前網(wǎng)站下所有的cookie(為避免跨域腳本(xss)攻擊,這個(gè)方法只能獲取非 HttpOnly 類型的cookie)。它會(huì)把所有的cookie通過一個(gè)分號(hào)+空格的形式串聯(lián)起來,例如username=chenfangxu; job=coding
修改 cookie
要想修改一個(gè)cookie,只需要重新賦值就行,舊的值會(huì)被新的值覆蓋。但要注意一點(diǎn),在設(shè)置新cookie時(shí),path/domain這幾個(gè)選項(xiàng)一定要舊cookie 保持一樣。否則不會(huì)修改舊值,而是添加了一個(gè)新的 cookie。
刪除
把要?jiǎng)h除的cookie的過期時(shí)間設(shè)置成已過去的時(shí)間,path/domain/這幾個(gè)選項(xiàng)一定要舊cookie 保持一樣。
注意
如果只設(shè)置一個(gè)值,那么算cookie中的value; 設(shè)置的兩個(gè)cookie,key值如果設(shè)置的相同,下面的也會(huì)把上面的覆蓋。
cookie的屬性(可選項(xiàng))
過期時(shí)間
如果我們想長(zhǎng)時(shí)間存放一個(gè)cookie。需要在設(shè)置這個(gè)cookie的時(shí)候同時(shí)給他設(shè)置一個(gè)過期的時(shí)間。如果不設(shè)置,cookie默認(rèn)是臨時(shí)存儲(chǔ)的,當(dāng)瀏覽器關(guān)閉進(jìn)程的時(shí)候自動(dòng)銷毀
<pre class="hljs language-dart" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">注意:document.cookie = '名稱=值;expires=' + GMT(格林威治時(shí)間)格式的日期型字符串; </pre>
一般設(shè)置天數(shù):new Date().setDate( oDate.getDate() + 5 ); 比當(dāng)前時(shí)間多5天
一個(gè)設(shè)置cookie時(shí)效性的例子
<pre class="hljs language-reasonml" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">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)</pre>
expires 是 http/1.0協(xié)議中的選項(xiàng),在新的http/1.1協(xié)議中expires已經(jīng)由 max-age 選項(xiàng)代替,兩者的作用都是限制cookie 的有效時(shí)間。expires的值是一個(gè)時(shí)間點(diǎn)(cookie失效時(shí)刻= expires),而max-age 的值是一個(gè)以秒為單位時(shí)間段(cookie失效時(shí)刻= 創(chuàng)建時(shí)刻+ max-age)。
另外,max-age 的默認(rèn)值是 -1(即有效期為 session );max-age有三種可能值:負(fù)數(shù)、0、正數(shù)。
負(fù)數(shù):有效期session;
0:刪除cookie;
正數(shù):有效期為創(chuàng)建時(shí)刻+ max-age
cookie的域概念(domain選項(xiàng))
domain指定了 cookie 將要被發(fā)送至哪個(gè)或哪些域中。默認(rèn)情況下,domain 會(huì)被設(shè)置為創(chuàng)建該 cookie 的頁面所在的域名,所以當(dāng)給相同域名發(fā)送請(qǐng)求時(shí)該 cookie 會(huì)被發(fā)送至服務(wù)器。
瀏覽器會(huì)把 domain 的值與請(qǐng)求的域名做一個(gè)尾部比較(即從字符串的尾部開始比較),并將匹配的 cookie 發(fā)送至服務(wù)器。
客戶端設(shè)置
document.cookie = "username=cfangxu;path=/;domain=qq.com"
如上:“www.qq.com" 與 "sports.qq.com" 公用一個(gè)關(guān)聯(lián)的域名"qq.com",我們?nèi)绻胱?"sports.qq.com" 下的cookie被 "www.qq.com" 訪問,我們就需要用到 cookie 的domain屬性,并且需要把path屬性設(shè)置為 "/"。
服務(wù)端設(shè)置
Set-Cookie: username=cfangxu;path=/;domain=qq.com
注:一定的是同域之間的訪問,不能把domain的值設(shè)置成非主域的域名。
cookie的路徑概念(path選項(xiàng))
cookie 一般都是由于用戶訪問頁面而被創(chuàng)建的,可是并不是只有在創(chuàng)建 cookie 的頁面才可以訪問這個(gè) cookie。
因?yàn)榘踩矫娴目紤],默認(rèn)情況下,只有與創(chuàng)建 cookie 的頁面在同一個(gè)目錄或子目錄下的網(wǎng)頁才可以訪問。
即path屬性可以為服務(wù)器特定文檔指定cookie,這個(gè)屬性設(shè)置的url且?guī)в羞@個(gè)前綴的url路徑都是有效的。
客戶端設(shè)置
最常用的例子就是讓 cookie 在根目錄下,這樣不管是哪個(gè)子頁面創(chuàng)建的 cookie,所有的頁面都可以訪問到了。
document.cookie = "username=cfangxu; path=/"
服務(wù)端設(shè)置
Set-Cookie:name=cfangxu; path=/blog
如上設(shè)置:path 選項(xiàng)值會(huì)與 /blog,/blogrool 等等相匹配;任何以 /blog 開頭的選項(xiàng)都是合法的。需要注意的是,只有在 domain 選項(xiàng)核實(shí)完畢之后才會(huì)對(duì) path 屬性進(jìn)行比較。path 屬性的默認(rèn)值是發(fā)送 Set-Cookie 消息頭所對(duì)應(yīng)的 URL 中的 path 部分。
domain和path總結(jié):
domain是域名,path是路徑,兩者加起來就構(gòu)成了 URL,domain和path一起來限制 cookie 能被哪些 URL 訪問。
所以domain和path2個(gè)選項(xiàng)共同決定了cookie何時(shí)被瀏覽器自動(dòng)添加到請(qǐng)求頭部中發(fā)送出去。如果沒有設(shè)置這兩個(gè)選項(xiàng),則會(huì)使用默認(rèn)值。domain的默認(rèn)值為設(shè)置該cookie的網(wǎng)頁所在的域名,path默認(rèn)值為設(shè)置該cookie的網(wǎng)頁所在的目錄。
cookie的安全性(secure選項(xiàng))
通常 cookie 信息都是使用HTTP連接傳遞數(shù)據(jù),這種傳遞方式很容易被查看,所以 cookie 存儲(chǔ)的信息容易被竊取。假如 cookie 中所傳遞的內(nèi)容比較重要,那么就要求使用加密的數(shù)據(jù)傳輸。
secure選項(xiàng)用來設(shè)置cookie只在確保安全的請(qǐng)求中才會(huì)發(fā)送。當(dāng)請(qǐng)求是HTTPS或者其他安全協(xié)議時(shí),包含 secure 選項(xiàng)的 cookie 才能被發(fā)送至服務(wù)器。
document.cookie = "username=cfangxu; secure"
把cookie設(shè)置為secure,只保證 cookie 與服務(wù)器之間的數(shù)據(jù)傳輸過程加密,而保存在本地的 cookie文件并不加密。就算設(shè)置了secure 屬性也并不代表他人不能看到你機(jī)器本地保存的 cookie 信息。機(jī)密且敏感的信息絕不應(yīng)該在 cookie 中存儲(chǔ)或傳輸,因?yàn)?cookie 的整個(gè)機(jī)制原本都是不安全的
注意:如果想在客戶端即網(wǎng)頁中通過 js 去設(shè)置secure類型的 cookie,必須保證網(wǎng)頁是https協(xié)議的。在http協(xié)議的網(wǎng)頁中是無法設(shè)置secure類型cookie的。
httpOnly
這個(gè)選項(xiàng)用來設(shè)置cookie是否能通過 js 去訪問。默認(rèn)情況下,cookie不會(huì)帶httpOnly選項(xiàng)(即為空),所以默認(rèn)情況下,客戶端是可以通過js代碼去訪問(包括讀取、修改、刪除等)這個(gè)cookie的。當(dāng)cookie帶httpOnly選項(xiàng)時(shí),客戶端則無法通過js代碼去訪問(包括讀取、修改、刪除等)這個(gè)cookie。
在客戶端是不能通過js代碼去設(shè)置一個(gè)httpOnly類型的cookie的,這種類型的cookie只能通過服務(wù)端來設(shè)置。
cookie的編碼
cookie其實(shí)是個(gè)字符串,但這個(gè)字符串中等號(hào)、分號(hào)、空格被當(dāng)做了特殊符號(hào)。所以當(dāng)cookie的 key 和 value 中含有這3個(gè)特殊字符時(shí),需要對(duì)其進(jìn)行額外編碼,一般會(huì)用escape進(jìn)行編碼,讀取時(shí)用unescape進(jìn)行解碼;當(dāng)然也可以用encodeURIComponent/decodeURIComponent或者encodeURI/decodeURI,查看關(guān)于編碼的介紹
第三方cookie
通常cookie的域和瀏覽器地址的域匹配,這被稱為第一方cookie。那么第三方cookie就是cookie的域和地址欄中的域不匹配,這種cookie通常被用在第三方廣告網(wǎng)站。為了跟蹤用戶的瀏覽記錄,并且根據(jù)收集的用戶的瀏覽習(xí)慣,給用戶推送相關(guān)的廣告。
關(guān)于第三方cookie和cookie的安全問題可以查看https://mp.weixin.qq.com/s/oOGIuJCplPVW3BuIx9tNQg
-
cookie推薦資源
localStorage(本地存儲(chǔ))
HTML5新方法,不過IE8及以上瀏覽器都兼容。
特點(diǎn)
- 生命周期:持久化的本地存儲(chǔ),除非主動(dòng)刪除數(shù)據(jù),否則數(shù)據(jù)是永遠(yuǎn)不會(huì)過期的。
- 存儲(chǔ)的信息在同一域中是共享的。
- 當(dāng)本頁操作(新增、修改、刪除)了localStorage的時(shí)候,本頁面不會(huì)觸發(fā)storage事件,但是別的頁面會(huì)觸發(fā)storage事件。
- 大小:據(jù)說是5M(跟瀏覽器廠商有關(guān)系)
- 在非IE下的瀏覽中可以本地打開。IE瀏覽器要在服務(wù)器中打開。
- localStorage本質(zhì)上是對(duì)字符串的讀取,如果存儲(chǔ)內(nèi)容多的話會(huì)消耗內(nèi)存空間,會(huì)導(dǎo)致頁面變卡
- localStorage受同源策略的限制
設(shè)置
localStorage.setItem('username','cfangxu');
獲取
localStorage.getItem('username')
也可以獲取鍵名
localStorage.key(0) #獲取第一個(gè)鍵名
刪除
localStorage.removeItem('username')
也可以一次性清除所有存儲(chǔ)
localStorage.clear()
storage事件
當(dāng)storage發(fā)生改變的時(shí)候觸發(fā)。
注意: 當(dāng)前頁面對(duì)storage的操作會(huì)觸發(fā)其他頁面的storage事件
事件的回調(diào)函數(shù)中有一個(gè)參數(shù)event,是一個(gè)StorageEvent對(duì)象,提供了一些實(shí)用的屬性,如下表:
| 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
其實(shí)跟localStorage差不多,也是本地存儲(chǔ),會(huì)話本地存儲(chǔ)
特點(diǎn):
- 用于本地存儲(chǔ)一個(gè)會(huì)話(session)中的數(shù)據(jù),這些數(shù)據(jù)只有在同一個(gè)會(huì)話中的頁面才能訪問并且當(dāng)會(huì)話結(jié)束后數(shù)據(jù)也隨之銷毀。因此sessionStorage不是一種持久化的本地存儲(chǔ),僅僅是會(huì)話級(jí)別的存儲(chǔ)。也就是說只要這個(gè)瀏覽器窗口沒有關(guān)閉,即使刷新頁面或進(jìn)入同源另一頁面,數(shù)據(jù)仍然存在。關(guān)閉窗口后,sessionStorage即被銷毀,或者在新窗口打開同源的另一個(gè)頁面,sessionStorage也是沒有的。
cookie、localStorage、sessionStorage區(qū)別
相同:在本地(瀏覽器端)存儲(chǔ)數(shù)據(jù)
-
不同:
localStorage、sessionStorage
localStorage只要在相同的協(xié)議、相同的主機(jī)名、相同的端口下,就能讀取/修改到同一份localStorage數(shù)據(jù)。
sessionStorage比localStorage更嚴(yán)苛一點(diǎn),除了協(xié)議、主機(jī)名、端口外,還要求在同一窗口(也就是瀏覽器的標(biāo)簽頁)下。
localStorage是永久存儲(chǔ),除非手動(dòng)刪除。
sessionStorage當(dāng)會(huì)話結(jié)束(當(dāng)前頁面關(guān)閉的時(shí)候,自動(dòng)銷毀)
cookie的數(shù)據(jù)會(huì)在每一次發(fā)送http請(qǐng)求的時(shí)候,同時(shí)發(fā)送給服務(wù)器而localStorage、sessionStorage不會(huì)。
擴(kuò)展其他的前端存儲(chǔ)方式(不常用)
web SQL database
先說個(gè)會(huì)被取代的,為什么會(huì)被取代,主要有以下幾個(gè)原因:
- W3C舍棄
Web SQL database草案,而且是在2010年年底,規(guī)范不支持了,瀏覽器廠商已經(jīng)支持的就支持了,沒有支持的也不打算支持了,比如IE和Firefox。 - 為什么要舍棄?因?yàn)?
Web SQL database本質(zhì)上是一個(gè)關(guān)系型數(shù)據(jù)庫,后端可能熟悉,但是前端就有很多不熟悉了,雖然SQL的簡(jiǎn)單操作不難,但是也得需要學(xué)習(xí)。 - SQL熟悉后,真實(shí)操作中還得把你要存儲(chǔ)的東西,比如對(duì)象,轉(zhuǎn)成SQL語句,也挺麻煩的。
IndexedDB
通俗地說,IndexedDB 就是瀏覽器提供的本地?cái)?shù)據(jù)庫,它可以被網(wǎng)頁腳本創(chuàng)建和操作。IndexedDB 允許儲(chǔ)存大量數(shù)據(jù),提供查找接口,還能建立索引。這些都是 LocalStorage 所不具備的。就數(shù)據(jù)庫類型而言,IndexedDB 不屬于關(guān)系型數(shù)據(jù)庫(不支持 SQL 查詢語句),更接近 NoSQL 數(shù)據(jù)庫。
IndexedDB 具有以下特點(diǎn)。
(1)鍵值對(duì)儲(chǔ)存。 IndexedDB 內(nèi)部采用對(duì)象倉庫(object store)存放數(shù)據(jù)。所有類型的數(shù)據(jù)都可以直接存入,包括 JavaScript 對(duì)象。對(duì)象倉庫中,數(shù)據(jù)以"鍵值對(duì)"的形式保存,每一個(gè)數(shù)據(jù)記錄都有對(duì)應(yīng)的主鍵,主鍵是獨(dú)一無二的,不能有重復(fù),否則會(huì)拋出一個(gè)錯(cuò)誤。
(2)異步。 IndexedDB 操作時(shí)不會(huì)鎖死瀏覽器,用戶依然可以進(jìn)行其他操作,這與 LocalStorage 形成對(duì)比,后者的操作是同步的。異步設(shè)計(jì)是為了防止大量數(shù)據(jù)的讀寫,拖慢網(wǎng)頁的表現(xiàn)。
(3)支持事務(wù)。 IndexedDB 支持事務(wù)(transaction),這意味著一系列操作步驟之中,只要有一步失敗,整個(gè)事務(wù)就都取消,數(shù)據(jù)庫回滾到事務(wù)發(fā)生之前的狀態(tài),不存在只改寫一部分?jǐn)?shù)據(jù)的情況。
(4)同源限制 IndexedDB 受到同源限制,每一個(gè)數(shù)據(jù)庫對(duì)應(yīng)創(chuàng)建它的域名。網(wǎng)頁只能訪問自身域名下的數(shù)據(jù)庫,而不能訪問跨域的數(shù)據(jù)庫。
(5)儲(chǔ)存空間大 IndexedDB 的儲(chǔ)存空間比 LocalStorage 大得多,一般來說不少于 250MB,甚至沒有上限。
(6)支持二進(jìn)制儲(chǔ)存。 IndexedDB 不僅可以儲(chǔ)存字符串,還可以儲(chǔ)存二進(jìn)制數(shù)據(jù)(ArrayBuffer 對(duì)象和 Blob 對(duì)象)
indexedDB 是一個(gè)基于JavaScript的面向?qū)ο蟮臄?shù)據(jù)庫。 IndexedDB允許你存儲(chǔ)和檢索用鍵索引的對(duì)象;
IndexedDB 鼓勵(lì)使用的基本模式如下所示:
打開數(shù)據(jù)庫并且開始一個(gè)事務(wù)。
創(chuàng)建一個(gè) object store。
構(gòu)建一個(gè)請(qǐng)求來執(zhí)行一些數(shù)據(jù)庫操作,像增加或提取數(shù)據(jù)等。
通過監(jiān)聽正確類型的 DOM 事件以等待操作完成。
在操作結(jié)果上進(jìn)行一些操作(可以在 request 對(duì)象中找到)
1、首先打開indexedDB數(shù)據(jù)庫
語法:
window.indexedDB.open(dbName, version)
var db;
// 打開數(shù)據(jù)庫,open還有第二個(gè)參數(shù)版本號(hào)
var request = window.indexedDB.open('myTestDatabase');
// 數(shù)據(jù)庫打開成功后
request.onsuccess = function (event) {
// 存儲(chǔ)數(shù)據(jù)結(jié)果,后面所有的數(shù)據(jù)庫操作都離不開它。
db = request.result;
}
request.onerror = function (event) {
alert("Why didn't you allow my web app to use IndexedDB?!");
}
// 數(shù)據(jù)庫首次創(chuàng)建版本,或者window.indexedDB.open傳遞的新版本(版本數(shù)值要比現(xiàn)在的高)
request.onupgradeneeded = function (event) {
}
onupgradeneeded事件: 更新數(shù)據(jù)庫的 schema,也就是創(chuàng)建或者刪除對(duì)象存儲(chǔ)空間,這個(gè)事件將會(huì)作為一個(gè)允許你處理對(duì)象存儲(chǔ)空間的 versionchange 事務(wù)的一部分被調(diào)用。在數(shù)據(jù)庫第一次被打開時(shí)或者當(dāng)指定的版本號(hào)高于當(dāng)前被持久化的數(shù)據(jù)庫的版本號(hào)時(shí),這個(gè) versionchange 事務(wù)將被創(chuàng)建。onupgradeneeded 是我們唯一可以修改數(shù)據(jù)庫結(jié)構(gòu)的地方。在這里面,我們可以創(chuàng)建和刪除對(duì)象存儲(chǔ)空間以及構(gòu)建和刪除索引。
2、構(gòu)建數(shù)據(jù)庫
IndexedDB 使用對(duì)象存儲(chǔ)空間而不是表,并且一個(gè)單獨(dú)的數(shù)據(jù)庫可以包含任意數(shù)量的對(duì)象存儲(chǔ)空間。每當(dāng)一個(gè)值被存儲(chǔ)進(jìn)一個(gè)對(duì)象存儲(chǔ)空間時(shí),它會(huì)被和一個(gè)鍵相關(guān)聯(lián)。
// 數(shù)據(jù)庫首次創(chuàng)建版本,或者window.indexedDB.open傳遞的新版本(版本數(shù)值要比現(xiàn)在的高)
request.onupgradeneeded = function (event) {
//之前咱們不是在success中得到了db了么,為什么還要在這獲取,
//因?yàn)樵诋?dāng)前事件函數(shù)執(zhí)行后才會(huì)去執(zhí)行success事件
var db = event.target.result;
// 創(chuàng)建一個(gè)對(duì)象存儲(chǔ)空間,keyPath是id,keyGenerator是自增的
var objectStore = db.createObjectStore('testItem',{keyPath: 'id',autoIncrement: true});
// 創(chuàng)建一個(gè)索引來通過id搜索,id是自增的,不會(huì)有重復(fù),所以可以用唯一索引
objectStore.createIndex('id','id',{unique: true})
objectStore.createIndex('name','name');
objectStore.createIndex('age','age');
//添加一條信息道數(shù)據(jù)庫中
objectStore.add({name: 'cfangxu', age: '27'});
}
注意: 執(zhí)行完后,在調(diào)試工具欄Application的indexedDB中也看不到,你得右鍵刷新一下。
創(chuàng)建索引的語法:
objectStore.createIndex(indexName, keyPath, objectParameters)
indexName:創(chuàng)建的索引名稱,可以使用空名稱作為索引。
keyPath:索引使用的關(guān)鍵路徑,可以使用空的keyPath, 或者keyPath傳為數(shù)組keyPath也是可以的。
objectParameters:可選參數(shù)。常用參數(shù)之一是unique,表示該字段值是否唯一,不能重復(fù)。例如,本demo中id是不能重復(fù)的,于是有設(shè)置:
3、添加數(shù)據(jù)
上面的代碼建好了字段,并且添加了一條數(shù)據(jù),但是我們?nèi)绻朐趏nupgradeneeded事件外面操作,接下來的步驟了。
由于數(shù)據(jù)庫的操作都是基于事務(wù)(transaction)來進(jìn)行,于是,無論是添加編輯還是刪除數(shù)據(jù)庫,我們都要先建立一個(gè)事務(wù)(transaction),然后才能繼續(xù)下面的操作。
語法: var transaction = db.transaction(dbName, "readwrite");
第一個(gè)參數(shù)是事務(wù)希望跨越的對(duì)象存儲(chǔ)空間的列表,可以是數(shù)組或者字符串。如果你希望事務(wù)能夠跨越所有的對(duì)象存儲(chǔ)空間你可以傳入一個(gè)空數(shù)組。如果你沒有為第二個(gè)參數(shù)指定任何內(nèi)容,你得到的是只讀事務(wù)。因?yàn)檫@里我們是想要寫入所以我們需要傳入 "readwrite" 標(biāo)識(shí)。
var timer = setInterval(function () {
if(db) {
clearInterval(timer);
// 新建一個(gè)事務(wù)
var transaction = db.transaction(['testItem'], 'readwrite');
// 打開一個(gè)存儲(chǔ)對(duì)象
var objectStore = transaction.objectStore('testItem');
// 添加數(shù)據(jù)到對(duì)象中
objectStore.add({ name: 'xiaoming', age: '12' });
objectStore.add({ name: 'xiaolong', age: '20' });
}
},100)
為什么要用一個(gè)間隔定時(shí)器? 因?yàn)檫@是一個(gè)demo,正常的是要有操作才能進(jìn)行數(shù)據(jù)庫的寫入,在我們的demo中,js執(zhí)行到transaction會(huì)比indexedDB的onsuccess事件回調(diào)快,導(dǎo)致會(huì)拿到db為undefined,所以寫了個(gè)間隔定時(shí)器等它一會(huì)。
4、獲取數(shù)據(jù)
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、修改數(shù)據(jù)
var transaction = db.transaction(['testItem'], 'readwrite');
var objectStore = transaction.objectStore('testItem');
var getRquest = objectStore.put({ name: 'chenfangxu', age: '27', id:1 });
// 修改了id為1的那條數(shù)據(jù)
6、刪除數(shù)據(jù)
var transaction = db.transaction(['testItem'], 'readwrite');
var objectStore = transaction.objectStore('testItem');
var getRquest = objectStore.delete(1);
// 刪除了id為1的那條數(shù)據(jù)
上面的例子執(zhí)行完后,一定一定要右鍵刷新indexedDB,它自己是不會(huì)變的。
IndexDB-----Class封裝
class IndexDBDemo {
db = null
constructor(name, storeOpt = {}, key = null, indexOpt = {}) {
if (!this.db) {
this.init(name, storeOpt, key, indexOpt)
}
return this.db
}
// 初始化
init (name, storeOpt = {}, key = null, indexOpt = {}) {
console.log('init')
const request = indexedDB.open(name)
return new Promise((res, reject) => {
request.onsuccess = () => {
this.db = request.result
}
request.onupgradeneeded = function ({ target: { result } }) {
this.db = result
if (!this.db.objectStoreNames.contains(name)) {
const req = this.db.createObjectStore(name, storeOpt)
if (key) {
req.createIndex(key, key, indexOpt)
}
}
res()
}
request.onerror = (error) => {
reject(error)
}
})
}
// 新增數(shù)據(jù)
add (name, data) {
return new Promise((result, reject) => {
const request = this.db.transaction([name], 'readwrite')
.objectStore(name)
.add(data)
request.onsuccess = (event) => {
result(event)
}
request.onerror = (error) => {
reject(error)
}
})
}
// 刪除數(shù)據(jù)
remove(name, key) {
return new Promise((result, reject) => {
const request = this.db.transaction([name], 'readwrite')
.objectStore(name)
.delete(key)
request.onsuccess = (event) => {
result(event)
}
request.onerror = (error) => {
reject(error)
}
})
}
// 修改數(shù)據(jù)
update (name, data) {
return new Promise((result, reject) => {
const request = this.db.transaction([name], 'readwrite')
.objectStore(name)
.put(data)
request.onsuccess = (event) => {
result(event)
}
request.onerror = (error) => {
reject(error)
}
})
}
// 根據(jù)主鍵查詢數(shù)據(jù)
search (name, index) {
return new Promise((result, reject) => {
const request = this.db.transaction([name])
.objectStore(name)
.get(index)
request.onsuccess = (event) => {
if (request.result) {
result(request.result)
} else {
console.log('no data')
reject()
}
}
request.onerror = (error) => {
console.log('事務(wù)失敗')
reject(error)
}
})
}
// 根據(jù)索引查詢數(shù)據(jù)
searchIndex(name, key, index) {
return new Promise((result, reject) => {
const request = this.db.transaction([name])
.objectStore(name)
.index(key)
.get(index)
request.onsuccess = (event) => {
if (request.result) {
result(request.result)
} else {
console.log('no data')
reject()
}
}
request.onerror = (error) => {
reject(error)
}
})
}
}
export {
IndexDBDemo
}
IndexDB已有庫---Dexie.js
<template>
<div class="wrap">
<button @click="add">新增</button>
<button @click="update">修改</button>
<button @click="remove">刪除</button>
<button @click="search">查詢ID</button>
<button @click="searchIndex">查詢索引</button>
<button @click="clear">刪除全部表數(shù)據(jù)</button>
</div>
</template>
<script>
import Dexie from 'dexie';
export default {
name: "Dexie",
data() {
return {
db: null,
age: 8,
useInfoU: {
id: 1,
name: 'rj',
age: 16
}
}
},
mounted() {
// 懶加載-新增時(shí)才可查看
this.db = new Dexie('friends');
this.db.version(1).stores({
friend: '++id, name, age', // Primary key and indexed props
});
},
methods: {
// 新增
add() {
this.age += 10
const useInfo = {
name: 'lrj',
age: this.age
}
this.db.friend.add(useInfo)
},
// 修改
update() {
this.db.friend.put(this.useInfoU)
},
// 刪除
remove() {
this.db.friend.delete(1)
},
// 查詢ID
async search() {
const res = await this.db.friend.get(1)
console.log(res)
},
// 條件查詢索引
async searchIndex() {
const res = await this.db.friend.where('age').above(30).toArray()
console.log(res)
},
// 關(guān)閉
clear() {
this.db.friend.clear()
}
}
};
</script>
<style scoped lang='less'>
.wrap{
display: flex;
align-content: center;
justify-content: center;
button{
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
border: 1px solid #409eff;
-webkit-appearance: none;
text-align: center;
box-sizing: border-box;
outline: none;
margin: 0;
transition: .1s;
font-weight: 500;
border-radius: 20px;
padding: 12px 23px;
font-size: 14px;
color: #fff;
background-color: #409eff;
margin-left: 10px;
}
}
</style>