摘要:本文將會詳細(xì)的介紹瀏覽器實現(xiàn)緩存控制的相關(guān)知識翁都,包括cookie碍论、session、localStorage柄慰、Cache-Control鳍悠、Expires、ETag坐搔、Last-Modified等概念藏研。
注:在看本文之前建議可以先看一下網(wǎng)頁中的登錄注冊功能是如何實現(xiàn)的,對前端和后端的數(shù)據(jù)交互有個大概的了解概行。
1蠢挡、cookie是什么 && cookie能干什么
- 給cookie一個定義吧:
Cookie(復(fù)數(shù)形態(tài)Cookies),中文名稱為“小型文本文件”或“小甜餅”。指某些網(wǎng)站為了辨別用戶身份而儲存在用戶本地終端(Client Side)上的數(shù)據(jù)(通常經(jīng)過加密)业踏。 ——摘自維基禽炬。
-
如何通俗的解釋:
- Cookie 是瀏覽器訪問服務(wù)器后,服務(wù)器傳給瀏覽器的一段數(shù)據(jù)勤家。
- 瀏覽器需要保存這段數(shù)據(jù)腹尖,不得輕易刪除。
- 此后每次瀏覽器訪問該服務(wù)器,都必須帶上這段數(shù)據(jù)惕艳。
-
所以就能得出cookie的特點:
- 服務(wù)器通過設(shè)置
Set-Cookie
響應(yīng)頭來設(shè)置 cookie - 瀏覽器得到 cookie 后蛉谜,每次同源的請求的請求頭都會帶上 cookie
- 服務(wù)器讀取 cookie 就知道了登錄用戶的信息(如賬戶名等)
- cookie 實際上存儲在本地計算機(jī)的硬盤里
- cookie 的最大儲存量一般只有4K
- 服務(wù)器通過設(shè)置
-
那么cookie有哪些缺點呢:
- Cookie很容易被用戶篡改( Session 可以解決這個問題,防止用戶篡改)
- Cookie 的默認(rèn)有效期理論上在用戶關(guān)閉頁面后就失效断凶,實際上在在20分鐘左右,不同瀏覽器策略不同巫俺。但是后端可以強(qiáng)制設(shè)置有效期(如何設(shè)置見下文)认烁。
- Cookie 也有一定的同源策略,不過跟 AJAX 的同源策略稍微有些不同介汹。如:
- 當(dāng)請求 qq.com 下的資源時却嗡,瀏覽器會默認(rèn)帶上 qq.com 對應(yīng)的 Cookie,不會帶上 baidu.com 對應(yīng)的 Cookie
- 當(dāng)請求 v.qq.com 下的資源時嘹承,瀏覽器不僅會帶上 v.qq.com 的Cookie窗价,還會帶上 qq.com 的 Cookie
- 另外 Cookie 還可以根據(jù)路徑做限制,請自行了解叹卷,這個功能用得比較少撼港。
-
有了Cookie,我們就可以實現(xiàn)這兩件事 :
第一個作用是識別用戶身份骤竹。如:
比如用戶 A 用瀏覽器訪問了http://a.com帝牡,那么http://a.com 的服務(wù)器就會立刻給 A 返回一段數(shù)據(jù)「uid=1」(這就是 Cookie)。當(dāng) A 再次訪問 http://a.com的其他頁面時蒙揣,就會附帶上「uid=1」這段數(shù)據(jù)靶溜。這樣服務(wù)端就知道 A 是誰了。第二個作用是記錄歷史懒震。如:
假設(shè) http://a.com 是一個購物網(wǎng)站罩息,當(dāng) A 在上面將商品 B1 、B2 加入購物車時个扰,JS 可以改寫 Cookie瓷炮,改為「uid=1; cart=B1,B2」,表示購物車?yán)镉?B1 和 B2 兩樣商品了递宅。這樣一來娘香,當(dāng)用戶關(guān)閉網(wǎng)頁冬筒,過三天再打開網(wǎng)頁的時候,依然可以看到 B1 茅主、B2躺在購物車?yán)镂杼担驗闉g覽器并不會無緣無故地刪除這個 Cookie。(需要注意的是:上面的例子只是為了讓大家了解 Cookie 的作用而構(gòu)想出來的诀姚,實際的網(wǎng)站使用 Cookie 時會更謹(jǐn)慎一些响牛。為什么要非常謹(jǐn)慎的使用cookie請從下文尋找答案。)
-
那么我們?nèi)绾卧O(shè)置cookie呢:
其實只要一句話:在響應(yīng)頭中設(shè)置Set-Cookie
即可赫段,詳見Set-Cookie MDN 呀打。具體參數(shù)如下:-
Set-Cookie: <cookie-name>=<cookie-value>
普通的cookie,所有參數(shù)默認(rèn) -
Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date>
cookie 的最長有效時間糯笙,形式為符合 HTTP-date 規(guī)范的時間戳贬丛。 -
Set-Cookie: <cookie-name>=<cookie-value>; Max-Age=<non-zero-digit>
在 cookie 失效之前需要經(jīng)過的秒數(shù)。一位或多位非零(1-9)數(shù)字给涕。假如二者 (指 Expires 和Max-Age) 均存在豺憔,那么 Max-Age 優(yōu)先級更高。 -
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>
指定 cookie 可以送達(dá)的主機(jī)名够庙。假如沒有指定恭应,那么默認(rèn)值為當(dāng)前文檔訪問地址中的主機(jī)部分(但是不包含子域名)。與之前的規(guī)范不同的是耘眨,域名之前的點號會被忽略昼榛。假如指定了域名,那么相當(dāng)于各個子域名也包含在內(nèi)了剔难。 -
Set-Cookie: <cookie-name>=<cookie-value>; Path=<path-value>
指定一個 URL 路徑胆屿,這個路徑必須出現(xiàn)在要請求的資源的路徑中才可以發(fā)送 Cookie 首部。 -
Set-Cookie: <cookie-name>=<cookie-value>; Secure
一個帶有安全屬性的 cookie 只有在請求使用SSL和HTTPS協(xié)議的時候才會被發(fā)送到服務(wù)器偶宫。(注意:非安全站點(http:)已經(jīng)不能再在 cookie 中設(shè)置 secure 指令了) -
Set-Cookie: <cookie-name>=<cookie-value>; HttpOnly
設(shè)置了 HttpOnly 屬性的 cookie 不能使用 JavaScript 經(jīng)由Document.cookie
屬性非迹、XMLHttpRequest
和Request
APIs 進(jìn)行訪問,以防范跨站腳本攻擊(XSS)读宙。 Set-Cookie: <cookie-name>=<cookie-value>; SameSite=Strict
-
Set-Cookie: <cookie-name>=<cookie-value>; SameSite=Lax
上面兩個允許服務(wù)器設(shè)定一則 cookie 不隨著跨域請求一起發(fā)送彻秆,這樣可以在一定程度上防范跨站請求偽造攻擊(CSRF)楔绞。
-
-
如何刪除cookie
通過設(shè)置cookie的有效期在當(dāng)前時間之前结闸,就可以刪除cookie啦var delete_cookie = function(name) { document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:01 GMT;'; };
-
cookie 的
HttpOnly
屬性
為避免跨域腳本 (XSS)攻擊,通過JavaScript的Document.cookie
API無法訪問帶有HttpOnly
標(biāo)記的Cookie酒朵,它們只應(yīng)該發(fā)送給服務(wù)端桦锄。如果包含服務(wù)端 Session 信息的 Cookie 不想被客戶端 JavaScript 腳本調(diào)用,那么就應(yīng)該為其設(shè)置HttpOnly
標(biāo)記蔫耽。Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
2结耀、使用session保存信息
- 先舉個例子看如何使用session
之前的寫法:直接將數(shù)據(jù)放到cookie里面:
這樣寫一來會暴露用戶的個人信息留夜,二來用戶可以直接通過瀏覽器修改cookie,極有可能獲取到別人的用戶信息图甜,極不安全碍粥。因此出現(xiàn)了下面的使用session的操作:response.setHeader('Set-Cookie', `login_email=${email}`)
首先來解釋一下上面的代碼:即設(shè)置let sessions = {} let sessionId = Math.random() * 10000 // 設(shè)置sessionId 為一個隨機(jī)數(shù) sessions[sessionId] = {login_email:email} // 將email 存儲在sessions這個對象中 response.setHeader('Set-Cookie', `sessionId = ${sessionId}`) // cookie中存儲的是 sessionId 這個隨機(jī)數(shù)
cookie
的中存儲的值為一個隨機(jī)數(shù),當(dāng)后臺獲取到cookie
時黑毅,就可以獲取到該隨機(jī)數(shù)并在sessions
這個對象中查找key
為這個隨機(jī)數(shù)的value
嚼摩,即可知道用戶的郵箱是什么 。如圖所示矿瘦,此時的響應(yīng)頭是這樣的:
-
那么究竟如何定義session呢:
首先我們得知道session的本質(zhì):就是存儲在服務(wù)器上的一個哈希表枕面。- 將sessionID(隨機(jī)數(shù)) 通過 Cookie 發(fā)給客戶端
- 客戶端訪問服務(wù)器時,服務(wù)器讀取 sessionID
- 服務(wù)器有一塊內(nèi)存(哈希表)保存了所有 session
- 通過sessionID 后臺可以得到對應(yīng)用戶的隱私信息缚去,如 id潮秘,email
- 這塊內(nèi)存(哈希表)就是服務(wù)器上的所有 session
那么session相對于cookie就沒有缺點了嗎?
很明顯易结,答案是否定的枕荞。session最大的缺點就是占內(nèi)存,如果你的用戶量非常大搞动,服務(wù)器就需要足夠的容量來存儲數(shù)據(jù)买猖。-
不基于cookie能否實現(xiàn)session呢
答案是肯定的,可以通過設(shè)置網(wǎng)址的查詢參數(shù) + localStorage 來達(dá)成目的滋尉。具體過程如下:- 后端待用戶登陸后設(shè)置他的sessionID玉控,但不把它放在 cookie 里,而是將信息通過響應(yīng)體傳JSON給前端狮惜。
- 前端拿到響應(yīng)體中的JSON后將其轉(zhuǎn)換成對象(
JSON.parse
) - 將從JSON中獲取到的數(shù)據(jù)(如 sessionID)放在 localStorage 里面(localStorage里的數(shù)據(jù)目前暫時用不到)
- 以后跳轉(zhuǎn)到其他頁面(如首頁)時高诺,將 sessionID 放在 URL 的查詢參數(shù)里(如:
window.location.href = '/?sessionId=object.sessionId'
) - 那么進(jìn)入首頁后,該頁面的 URL 的查詢參數(shù)就帶上了你的sessionID
- 后端通過在用戶訪問首頁時碾篡,傳到服務(wù)器的 URL 虱而,來獲取到查詢參數(shù),從而獲取到用戶的 sessionID开泽,然后在數(shù)據(jù)庫中查到sessionID對應(yīng)的信息就可以知道用戶是誰牡拇。
3、localStorage是什么 & localStorage 怎么用
localStorage 是HTML5 技術(shù)提供的api 穆律,是window對象下的一個方法(window.localStorage
)惠呼。
localStorage 的實質(zhì)是一個存儲在計算機(jī)本地的哈希表。
-
localStorage常用的api
-
localStorage.setItem('myCat', 'Tom')
訪問當(dāng)前域名下的本地Storage
對象峦耘,并增加了一個數(shù)據(jù)項通過使用Storage.setItem()
作為 Storage 接口的方法剔蹋,接受一個鍵名和值作為參數(shù),將會把鍵名添加到存儲中辅髓,如果鍵名已存在泣崩,則更新其對應(yīng)的值少梁。 -
let cat = localStorage.getItem('myCat')
該語法用于讀取 localStorage 項 -
localStorage.removeItem('myCat')
該語法用于移除 localStorage 項 -
localStorage.clear()
該語法用于移除所有的 localStorage 項
-
需要注意的是:localStorage 只能存 string,所以如果想存Object矫付,就需要用JSON來存凯沪,舉個例子:
localStorage.setItem('jsonObj',JSON.stringify({name:'obj'}))
然后如果想要解析這個 JSON 將其轉(zhuǎn)換成對象可以使用JSON.parse()
-
那么localStorage怎么使用呢:
- 首先我們需要知道JS中的變量有這樣的問題:變量只在當(dāng)前的會話期內(nèi)有效。即只要刷新頁面买优,之前存儲的變量就被回收了著洼。那么我們?nèi)绾巫屩八鎯Φ淖兞吭谒⑿马撁嬷筮€存在呢,所以就有了localStorage 來解決這個需求而叼。
- 來看一個小demo身笤,這個demo通過使用 localStorage 完成了變量的持久化存儲,因為localStorage實際上是存儲在本地計算機(jī)中的葵陵,不會因為頁面刷新就導(dǎo)致變量被回收液荸。
let a = localStorage.getItem('a') if(!a){ a = 0 }esle{ a = parseInt(a,10) + 1 // 如果不進(jìn)行轉(zhuǎn)換,將會變成字符串的相加 } console.log(a) localStorage.setItem('a',a) // 每次刷新頁面 脱篙,a 的值都會加 1
- 常見的使用情景:用戶第一次進(jìn)入頁面時提示用戶一些信息娇钱,第二次進(jìn)入以后就不再提示。 示例代碼如下:
let already = localStorage.getItem('isPrompt') if(!already){ alert('我們的頁面改版啦') localStorage.setItem('isPrompt', true) }esle{ // 已經(jīng)提示了就什么也不做 }
-
localStorage的特點
- localStorage 與 HTTP 無關(guān)
- HTTP 不會帶上 localStorage 的值
- 只有相同域名的頁面才能互相讀取 localStorage(遵循同源策略)
- 每個域名 localStorage 最大存儲量為 5Mb 左右(每個瀏覽器不一樣)
- 常用場景:記錄有沒有提示過用戶(記錄一些不敏感的信息)
- localStorage 理論上永久有效绊困,除非用戶清理緩存文搂,無法設(shè)置過期時間
-
sessionStorage 和 localStorage 有什么關(guān)系和區(qū)別
- 上一條記錄的 localStorage 的特點,1~4條二者都有秤朗。
- 區(qū)別:sessionStorage 在用戶關(guān)閉頁面后(即 session 結(jié)束后或者說會話結(jié)束后)就失效煤蹭,而且沒辦法控制;而 localStorage 理論上永久有效取视。
- sessionStorage 的 api 和 localStorage 完全一樣硝皂。如
setItem()
、getItem()
作谭、remove()
稽物、clear()
4、關(guān)于上面的概念需要注意的事情
cookie 和 session 有什么關(guān)系:
答:一般來說 cookie 是基于 session 實現(xiàn)的折欠。cookie是存在客戶端本地的贝或,而session是保存在服務(wù)器上的。cookie 和 localStorage 的區(qū)別是什么
答:cookie 每次請求會被帶給服務(wù)器锐秦,而 localStorage不會咪奖;cookie的最大儲存量一般只有4k,而localStorage 一般有5Mb 农猬;cookie的有效期一般在用戶關(guān)閉頁面后就失效赡艰,而localStorage理論上永久有效。可能你就要問了斤葱,看起來cookie 和 localStorage 幾乎沒有關(guān)系慷垮,為什么要放在一起比較?
這是由歷史原因的:localStorage 是新API揍堕,在它出現(xiàn)以前料身,之前的前端如何實現(xiàn)跨頁面的數(shù)據(jù)持久化存儲呢?只能通過cookie 衩茸,要知道cookie 和 localStorage 都是存放在計算機(jī)本地芹血,所以當(dāng)時很多程序員都把數(shù)據(jù)放在cookie里,但是有個問題楞慈,你所有存在 cookie里面的東西幔烛,每次請求都會帶到服務(wù)器里面去,如果cookie里面放的東西太多囊蓝,那么每次請求就要花費更多的時間饿悬。所以要達(dá)到跨頁面的數(shù)據(jù)持久化存儲,最優(yōu)解就是使用 localStorage聚霜。前端永遠(yuǎn)不要讀或者寫cookie狡恬,讀寫cookie一系列的操作是后端需要完成的工作。(這里的前后端指的是代碼上的分離而不是人員上的分離)
5蝎宇、HTTP緩存 & Cache-Control 緩存控制
需要提一句的是:緩存控制其實也是前端性能優(yōu)化的一部分弟劲,所以這一點其實很值得關(guān)注。
緩存控制的使用場景:一些網(wǎng)站需要加載的資源很多姥芥,導(dǎo)致每次刷新頁面速度都非常都慢兔乞,那么該如何加快請求速度,緩存控制就應(yīng)運而生凉唐。
Cache-Control 通用消息頭被用于在http 請求和響應(yīng)中通過指定指令來實現(xiàn)緩存機(jī)制报嵌。緩存指令是單向的, 這意味著在請求設(shè)置的指令,在響應(yīng)中不一定包含相同的指令熊榛。
最常用的響應(yīng)指令:Cache-Control: max-age=<seconds>
:
- 設(shè)置緩存存儲的最大周期锚国,超過這個時間緩存被認(rèn)為過期(單位秒)。與Expires相反玄坦,時間是相對于請求的時間血筑。即在設(shè)置的時間內(nèi),請求相同的URL將不會把請求發(fā)送到服務(wù)器煎楣,瀏覽器會阻斷這個請求(即這個請求實際并沒有發(fā)送出去)豺总,然后然后之間展示緩存過的上一次的數(shù)據(jù)(存儲在本地的硬盤或內(nèi)存)。實際開發(fā)中择懂,我們一般設(shè)置為一年31536000秒以上喻喳。
- 需要注意的是:一般首頁(甚至包括所有的html頁面)不應(yīng)該設(shè)置緩存(chrome瀏覽器甚至?xí)苯咏眠@個設(shè)置,也就是說你給首頁設(shè)置Cache-Control也不會生效)困曙。因為如果你連首頁都設(shè)置了緩存表伦,用戶即使刷新頁面谦去,也不會向服務(wù)器發(fā)送任何請求,那么如果你的代碼更新了蹦哼,用戶在緩存期間將始終無法獲取到最新的版本鳄哭。
- 實際開發(fā)中的使用:html頁面不設(shè)置Cache-Control,其他資源Cache-Control設(shè)置一年以上纲熏,如果代碼更新(比如js或css)妆丘,該資源的請求的url的查詢參數(shù)加上一個版本號,或者文件名后加隨機(jī)數(shù)即可局劲。也就是說勺拣,只要你的請求的url有一點點不一樣,就不會使用緩存
6鱼填、Expires 是什么 & 有什么不好的地方
Expires 也是用來控制緩存的药有,但是現(xiàn)在在開發(fā)中,優(yōu)先使用Cache-Control剔氏。
Expires 響應(yīng)頭包含日期/時間塑猖, 即在此時候之后,緩存過期谈跛。如何使用Expires呢:
設(shè)置響應(yīng)頭羊苟,如:Expires:"Wed, 22 Aug 2018 07:43:17 GMT"
后面接的是格林尼治時間。當(dāng)過了設(shè)置的時間后緩存就過期了感憾。所以
Expires
與Cache-Control
的區(qū)別就是:前者設(shè)置的是什么時候過期蜡励,后者設(shè)置的是過了多久過期。需要注意的是:如果在
Cache-Control
響應(yīng)頭設(shè)置了 "max-age" 或者 "s-max-age" 指令阻桅,那么Expires
頭會被忽略凉倚。那么為什么 Cache-Control 優(yōu)先級比Expire 高呢?因為 Cache-Control 相對于 Expires 是個更新的技術(shù)嫂沉, 而且使用 Expires 是存在bug 的稽寒。
Expires 的bug:時間使用的是計算機(jī)本地的時間,如果某一天計算機(jī)時間錯亂趟章,本地時間一直在你設(shè)置的時間之后杏糙,那么他就永遠(yuǎn)使用不了緩存。
7蚓土、ETag 是什么 & MD5 是什么
首先宏侍,Etag 是用來給文件一個版本號的。
那么我們先來了解一下MD5 蜀漆。MD5是一個消息摘要算法谅河。MD5 的常見使用場景:你在網(wǎng)上下載一個很大的文件,下載過程中你怎么知道自己下載的對不對呢?所以MD5 就是為了這種情景而生的绷耍。即網(wǎng)上的文件除了有資源本身外吐限,還會有一個MD5值,然后你下載到本地后的文件也可以算出一個MD5 值锨天,然后二者對比毯盈,如果完全相同則說明下的文件是正確的剃毒。
Etag 的使用場景:
- 后端算出資源的MD5值病袄,將其設(shè)置到響應(yīng)頭的Etag里,如:
Etag:"33a64df551425fcc55e4d42a148795d9f25f89d4"
- 然后下一次請求時赘阀,里面這個資源的請求頭就多了一個值:
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
- 后端就有如下設(shè)置:
如果請求頭的If-None-Match
中的值和資源的MD5一樣益缠,說明資源是最新的,不需要下載基公,即可以返回304狀態(tài)碼( Not Modified)幅慌,然后在此分支下就不用設(shè)置響應(yīng)體了。 - 如果MD5的值不一樣轰豆,說明你的資源需要更新胰伍,此時再返回最新的資源作為響應(yīng)體。
Etag 與 Cache-Control 的區(qū)別:
- Cache-Control 直接不會發(fā)請求酸休,只要url一樣骂租,直接使用緩存了的數(shù)據(jù)
- Etag 則每一次都會請求,只不過如果資源的MD5一樣斑司,就不下載
8渗饮、Last-Modified 是什么 & 使用過程是怎么樣的
Last-Modified
是一個響應(yīng)首部,其中包含源頭服務(wù)器認(rèn)定的資源做出修改的日期及時間宿刮。 它通常被用作一個驗證器來判斷接收到的或者存儲的資源是否彼此一致互站。由于精確度比 ETag
要低,所以這是一個備用機(jī)制僵缺。包含有 If-Modified-Since
或 If-Unmodified-Since
首部的條件請求會使用這個字段胡桃。
使用過程:
- 在瀏覽器第一次請求某一個URL時,服務(wù)器端的返回狀態(tài)會是200磕潮,內(nèi)容是你請求的資源翠胰,同時有一個Last-Modified的屬性標(biāo)記在響應(yīng)頭里,此文件在服務(wù)期端最后被修改的時間揉抵,格式類似這樣:
Last-Modified:Tue, 24 Feb 2009 08:01:04 GMT
- 客戶端第二次請求此URL時亡容,根據(jù)HTTP協(xié)議的規(guī)定,瀏覽器會向服務(wù)器傳送If-Modified-Since請求頭冤今,詢問該時間之后文件是否有被修改過:
If-Modified-Since:Tue, 24 Feb 2009 08:01:04 GMT
- 如果服務(wù)器端的資源沒有變化闺兢,則自動返回HTTP 304(NotChanged.)狀態(tài)碼,內(nèi)容為空,這樣就節(jié)省了傳輸數(shù)據(jù)量屋谭。當(dāng)服務(wù)器端代碼發(fā)生改變或者重啟服務(wù)器時脚囊,則重新發(fā)出資源,返回和第一次請求時類似桐磁。從而保證不向客戶端重復(fù)發(fā)出資源悔耘,也保證當(dāng)服務(wù)器有變化時,客戶端能夠得到最新的資源我擂。
(END)