在正式介紹cookie之前我們要先來說一說網(wǎng)絡(luò)通訊協(xié)議
首先:什么是網(wǎng)絡(luò)通訊協(xié)議?所謂協(xié)議一般就是甲乙雙方溝通要遵循的規(guī)則與方式,那么通訊協(xié)議就是通訊雙方要遵循的規(guī)則,網(wǎng)絡(luò)通訊協(xié)議則是計算機(jī)雙方傳輸數(shù)據(jù)所要遵循的協(xié)議,瀏覽網(wǎng)頁時, 一般使用http或者h(yuǎn)ttps,而它們都是基于TCP/IP協(xié)議的。
TCP/IP協(xié)議是面向連接的
在發(fā)送數(shù)據(jù)之前,都必須先在雙方之間建立一條連接。在TCP/IP協(xié)議中,TCP 協(xié)議提供可靠的連接服務(wù),連接是通過三次握手進(jìn)行初始化的,如下圖:
客戶端要向服務(wù)器發(fā)送請求,首先會發(fā)送一個請求連接,這是第一次握手;
當(dāng)服務(wù)器收到這個請求以后返回給客戶端一條消息,這是第二次握手;
然后客戶端再把確認(rèn)消息發(fā)給服務(wù)器,這是第三次握手;
經(jīng)過這三次握手客戶端與服務(wù)器之間的連接就建立起來了。
就像打電話一樣,客戶端向服務(wù)器撥通電話,服務(wù)器接起電話,說了一句:“喂?”,然后客戶端收到了這句“喂”,再跟服務(wù)器說一聲“喂!”,連接就建立好了,可以愉快的通話了
建立連接需要經(jīng)過三次握手,那數(shù)據(jù)傳輸結(jié)束以后呢?我們打電話事情說完了要掛掉電話要經(jīng)歷一個怎樣的過程?可能對話是下面這樣的:
A:“我掛了”
B:“好”
A:“拜拜”
B:“拜拜,我也掛了”
A:“好,拜拜”
B:“嗯嗯,拜拜,掛了”
A:“好,拜拜”
B:“拜拜”
有點搞笑 ,經(jīng)過好多次消息確認(rèn)好不容易才能把電話掛掉,但這并不是無聊,而是要互相確認(rèn)對方是否真的已經(jīng)準(zhǔn)備斷開連接了,那么我們的TCP/IP協(xié)議也會考慮到這一點,要斷開連接會經(jīng)過四次揮手,如下圖:
數(shù)據(jù)傳輸結(jié)束時,客戶端向服務(wù)器發(fā)送一條結(jié)束的消息,服務(wù)器收到消息后會馬上返回給客戶端確認(rèn)消息,并隨后發(fā)送一條“我也結(jié)束了”的消息,當(dāng)客戶端收到這條消息以后會響應(yīng)給服務(wù)器確認(rèn)消息,服務(wù)器收到這條確認(rèn)消息以后就會關(guān)閉,而客戶端則會等待兩個單位時間之后關(guān)閉。注意這里客戶端并不是立馬關(guān)閉,而是會等帶兩個單位時間,為什么是兩個單位時間呢?首先,所謂的單位時間指的是一次前后端數(shù)據(jù)傳輸所花費的時間,這里要等待兩個單位時間是因為客戶端要等數(shù)據(jù)發(fā)送到服務(wù)器以后確認(rèn)收不到數(shù)據(jù)返回,這一來一去就是兩個單位時間了,因為如果這個時候服務(wù)器并沒有結(jié)束的話會再次給客戶端響應(yīng),而客戶端在兩個單位時間后收不到響應(yīng)則代表本次數(shù)據(jù)傳輸正式結(jié)束。
下面貼出兩張動圖:
這兩張動圖對于計算機(jī)專業(yè)的讀者可以嘗試探究,本文不做詳細(xì)展開
http無狀態(tài)
HTTP協(xié)議, 使我們用戶上網(wǎng)常見的協(xié)議, 簡單的理解, 它是基于TCP/IP協(xié)議改造加工出來的,也就是說每一個http請求都會通過三次握手建立連接,傳輸數(shù)據(jù), 然后四次揮手?jǐn)嚅_連接.
例如當(dāng)你登錄淘寶的時候, 你的瀏覽器跟淘寶服務(wù)器之間就行了 一個 http鏈接.
然后, 當(dāng)首頁數(shù)據(jù)加載完成, 鏈接就斷開了.
這時服務(wù)就無法保存你的狀態(tài). 你瀏覽了什么商品? 購物車加了幾個東西? 有沒有關(guān)閉瀏覽器? 服務(wù)器統(tǒng)統(tǒng)不知道了
所以, 我們說:http協(xié)議是一種無狀態(tài)協(xié)議,這里的無狀態(tài)指的是無法保存狀態(tài),因為一旦數(shù)據(jù)傳輸結(jié)束連接就會斷開,就無法獲取一些狀態(tài)了,這樣設(shè)計的好處就是可以節(jié)約資源,需要的時候才發(fā)出請求,但是也會造成一些困擾,
尤其是關(guān)于身份驗證. 用戶在登錄頁面完成了登錄,那么接下來就會回到首頁或者其他頁面進(jìn)行操作,但問題是這個時候登錄的這次http請求已經(jīng)結(jié)束了,所以當(dāng)完成頁面跳轉(zhuǎn)之后就取不到登錄狀態(tài)了,那怎么辦?再登錄一次?
當(dāng)你反復(fù)使用http協(xié)議跟服務(wù)器建立鏈接的時候, 服務(wù)器怎么才能知道, 你是一個登錄過的合法用戶?? 為了解決這個問題, 出現(xiàn)了cookie技術(shù).
cookie
cookie稱之為會話跟蹤技術(shù),顧名思義,就是在一次會話中跟蹤記錄一些狀態(tài)。首先,所謂的”會話“指的就是從瀏覽器打開一個網(wǎng)站到訪問它的其他網(wǎng)頁直到瀏覽器關(guān)閉的這個過程。
cookie就可以在一次會話從開始到結(jié)束的整個過程,全程跟蹤記錄客戶端的狀態(tài)(例如:是否登錄、購物車信息、是否已下載、是否 已點贊、視頻播放進(jìn)度等等)。
cookie是服務(wù)器存儲在客戶端本地的一個文件. 它就好比服務(wù)器發(fā)給客戶端的一個身份標(biāo)識, 有了這個身份牌, 只要客戶端隨身攜帶這個身份牌. 服務(wù)器就能識別我們的身份了
cookie的特點
1. 只能存儲文本
2. 單條存儲有大小限制4KB左右
3. 數(shù)量限制(一般瀏覽器,限制大概在50條左右)
4. 讀取有域名限制:不可跨域讀取,只能由來自 寫入cookie的 同一域名 的網(wǎng)頁可進(jìn)行讀取。簡單的講就是,哪個服務(wù)器發(fā)給你的cookie,只有哪個服務(wù)器有權(quán)利讀取(身份牌是我的,當(dāng)然只有我能讀取,你媳婦兒的手機(jī)自動連接了鄰居老王家的wifi,你知道這意味著什么嗎?)
5. 時效限制:每個cookie都有時效,默認(rèn)的有效期是,會話級別:就是當(dāng)瀏覽器關(guān)閉,那么cookie立即銷毀,但是我們也可以在存儲的時候手動設(shè)置cookie的過期時間
6. 路徑限制:存cookie時候可以指定路徑,只允許子路徑讀取外層cookie,外層不能讀取內(nèi)層。一般cookie都存在項目的根目錄,這樣就可以避免這種問題。
cookie的使用
其實, 如果是單純的身份驗證場景下, 前端開發(fā)是用不到cookie的
從服務(wù)器發(fā)送cookie給瀏覽器, 瀏覽器保存cookie, 到瀏覽器每次發(fā)送請求 ,攜帶著cookie文件, 這些事情瀏覽器全部都可以自己完成. 根本不需要我們前端開發(fā)人員參與.
但我們有時也會利用cookie, 在前端存儲一些狀態(tài)信息.
比如某個視頻看到了32分15秒.
我們可以通過前端操作, 直接將這個數(shù)據(jù)保存在本地cookie文件中
下次打開網(wǎng)站, 我們再次讀取這個信息
然后直接向服務(wù)器請求32分16秒的視頻內(nèi)容.
前端如何通過JS操作cookie
cookie的使用方式很簡單 ,系統(tǒng)提供的只有一個屬性 document.cookie,無論是存還是取或者其他操作都是通過這一個屬性來完成(注:cookie是http協(xié)議下的技術(shù),所以不要用file的方式打開本地html文件測試cookie,雖然由部分瀏覽器也在file協(xié)議下實現(xiàn)了cookie,但是不推薦這么做)。
首先,我們來看看如何存:
document.cookie = 'username=dary' // 存一條username為dary的cookie
但是如果當(dāng)我們要存一條中文的cookie,比如:username=張三,在部分低版本瀏覽器就會遇到一些位置錯誤,這時就可以使用 encodeURIComponent 編碼對中文進(jìn)行編碼:
document.cookie = `username=encodeURIComponent('大林')`
可以看到,中文內(nèi)容已經(jīng)被編碼了,以后取得時候我們可以通過decodeURIComponent 方法進(jìn)行解碼,下文會提到
cookie的時效性
現(xiàn)在我們回頭去看看上面我們存過的cookie,其中有Expires/max-Age選項,這一項指的就是cookie的有效期,我們可以看到是session,代表會話期,也就是默認(rèn)的會話結(jié)束cookie失效,這時我們重啟瀏覽器就看不到這條cookie了。
除了默認(rèn)的會話過期我們還可以手動設(shè)置cookie的過期時間,比如:7天后過期
var date = new Date()
date.setDate(date.getDate() + 7)
document.cookie = `username=${encodeURIComponent('大林')};expires=${date.toUTCString()}`
我們可以看到過期時間已經(jīng)是7天以后了,這里我用了toUTCString()方法轉(zhuǎn)成了標(biāo)準(zhǔn)時區(qū),所以比北京時間快8個小時,這時我們關(guān)閉瀏覽器再次打開,仍然可以看到這條cookie
cookie的存儲路徑
我們來測試一下路徑,隨便進(jìn)入項目中某一個目錄或者新建一個目錄,然后把一下代碼放進(jìn)去執(zhí)行
document.cookie = 'username=dary'
我們可以看到,path不再是之前的 / 了,而是當(dāng)前目錄,這時我們再回到首頁,你會發(fā)現(xiàn),這條cookie不見了,因為外層時訪問不了內(nèi)層目錄存的cookie的。
所以我們一般存cookie都會這么寫:
document.cookie = 'username=dary;path=/'
不管當(dāng)前目錄是誰統(tǒng)一存根目錄,這樣項目中任意位置都可以訪問這一條cookie,這就perfect 啦!
由此:我們可以封裝一個存儲cookie的方法如下:
/** 存一條cookie
* @param {string} key 要存的cookie的名稱
* @param {string} value 要存的cookie的值
* @param {object} [options] 可選參數(shù),過期時間和目錄,如:{ path: '/', expires: 7 }存根目錄7天過期的cookie
*/
function setCookie (key, value, options) {
var str = `${key}=${encodeURIComponent(value)}`
// 先判斷options是否傳進(jìn)來了
if (options) {
// 如果傳進(jìn)來了再判斷有哪個屬性
if (options.path) {
// 路徑拼接進(jìn)去
str += `;path=${options.path}`
}
if (options.expires) {
var date = new Date()
// 日期設(shè)置為過期時間
date.setDate(date.getDate() + options.expires)
str += `;expires=${date.toUTCString()}`
}
}
document.cookie = str
}
取cookie
現(xiàn)在我們掌握了如何存cookie,接下來看看怎么取吧
取cookie同樣使用document.cookie這個屬性:
console.log(document.cookie) // username=dary
但是如果我們在一個網(wǎng)站里存了多條cookie,這個時候得到的結(jié)果就是
console.log(document.cookie) // username=dary; age=18
多條cookie之間以; 隔開,注意:這里是分號和一個空格,這個對我們拆開每一條cookie非常重要。所以我們現(xiàn)在希望能把cookie每一條拆開,得到一個對象,這樣就可以取得某一條cookie的值了,所以我們可以封裝一個獲取cookie的方法如下:
/** 獲取某一條cookie
* @param {string} key 要獲取的cookie的名稱
* @retrun {string} 當(dāng)前這條cookie的值
*/
getCookie (key) {
var str = document.cookie
var arr = str.split('; ')
var obj = new Object()
arr.forEach(item => {
var subArr = item.split('=')
obj[subArr[0]] = decodeURIComponent(subArr[1])
})
return obj[key]
}
刪除cookie
刪除cookie的方式特別簡單,我們只需要把cookie的過期時間設(shè)置為已經(jīng)過去了的時間就行了,這個時候瀏覽器一看,誒?這條cookie不是已經(jīng)過期了么?就只能把它刪掉了 ,方法如下:
/** 刪一條cookie
* @param {string} key 要刪的cookie的名稱
* @param {path} [path] 可選參數(shù),要刪的cookie的所在的路徑,如果就是當(dāng)前路徑這個參數(shù)可以不傳
*/
function removeCookie (key, path) {
var date = new Date()
date.setDate(date.getDate() - 1) // 過期時間設(shè)置為昨天
var str = `${key}='';expires=${date.toUTCString()}`
if (path) {
str += `;path=${path}`
}
document.cookie = str
}
修改cookie
重新存一下把之前的覆蓋掉就是修改了cookie了
最后附上一個cookie方法,既可以完成存也可以取,甚至可以刪
// 通過判斷有沒有傳第二個參數(shù)value來決定是存還是取
// 這個方法也可以用域刪cookie,比如:cookie('username', '', { expires: -1, path: '/' })
function cookie (key, value, options) {
if (value) {
var str = `${key}=${encodeURIComponent(value)}`
if (options) {
if (options.path) {
str += `;path=${options.path}`
}
if (options.expires) {
var date = new Date()
date.setDate(date.getDate() + options.expires)
str += `;expires=${date.toUTCString()}`
}
}
document.cookie = str
} else {
var str = document.cookie
var arr = str.split('; ')
var obj = new Object()
arr.forEach(item => {
var subArr = item.split('=')
obj[subArr[0]] = decodeURIComponent(subArr[1])
})
return obj[key]
}
}