Cookie在前端中的實踐
對于很多不了解后端的前端er來說,很多涉及后端的知識點都是一道坎,因為不懂后端,所以很多知識都只能在文章上得到,卻很少有機(jī)會實踐,導(dǎo)致很多這塊的知識點都是模模糊糊的。在這里,推薦大家去學(xué)習(xí)一下 Node.js,嘗試一下就知道,用 Node.js 搭建一個 mock 服務(wù)器是多么簡單的一件事情,新世界的大門就此敞開。
廢話說太多了,回到本篇文章的主題,來玩玩 Cookie。
環(huán)境配置
在開始聊 Cookie 之前,我們需要搭建一個本地服務(wù)器,我們將用這個服務(wù)器來操作 Cookie。
- // 首先打開命令行工具,執(zhí)行下面一些列命令
- mkdir cookie-demo && cd cookie-demo
- npm init
- npm install express --save
- touch main.js
執(zhí)行完上面一系列的命令之后,你就能看到如下的文件結(jié)構(gòu),我們需要操作的文件就是 main.js
- cookie-demo
- |- main.js
- |- node_modules
- |- package.json
打開 main.js,并在 main.js 寫入以下代碼:
- const express = require('express')
- const app = express()
- app.listen(3000, err => {
- if (err) {
- return console.log(err)
- }
- console.log('---- 打開 http://localhost:3000 吧----')
- })
- app.get('/', (req, res) => {
- res.send('<h1>hello world!</h1>')
- })
- // 在命令行執(zhí)行
- node main.js
- // 一個本地服務(wù)就跑起來了,現(xiàn)在打開 http://localhost:3000
- // 就可以看到一個大大的 hello world!
Cookie 是怎樣工作的
在介紹 Cookie 是什么之前,我們來看看 Cookie 是如何工作的:
1. 首先,我們假設(shè)當(dāng)前域名下還是沒有 Cookie 的
2. 接下來,瀏覽器發(fā)送了一個請求給服務(wù)器(這個請求是還沒帶上 Cookie 的)
3. 服務(wù)器設(shè)置 Cookie 并發(fā)送給瀏覽器(當(dāng)然也可以不設(shè)置)
4. 瀏覽器將 Cookie 保存下來
5. 接下來,以后的每一次請求,都會帶上這些 Cookie,發(fā)送給服務(wù)器
驗證
我們來驗證一下。
- // 修改 main.js
- app.get('/', (req, res) => {
- // 服務(wù)器接收到請求,在給響應(yīng)設(shè)置一個 Cookie
- // 這個 Cookie 的 name 為 testName
- // value 為 testValue
- res.cookie('testName', 'testValue')
- res.send('<h1>hello world!</h1>')
- })
- // 保存之后,重啟服務(wù)器
- // node main.js
現(xiàn)在打開 http://localhost:3000
- 我們看到 Request Headers 并沒有 Cookie 這個字段
- 但是 Response Headers 有了 Set-Cookie 這個字段
現(xiàn)在我們刷新一下頁面,相當(dāng)于重新向 http://localhost:3000/ 這個地址發(fā)起了一次請求。
現(xiàn)在我們就可以看到 Cookie 字段已經(jīng)帶上了,再刷新幾次看 Cookie 也還是在的。
document.cookie
JS 提供了獲取 Cookie 的方法:document.cookie,我們先去設(shè)置多幾個 Cookie。
- app.get('/', (req, res) => {
- res.cookie('testName0', 'testValue0')
- res.cookie('testName1', 'testValue1')
- res.cookie('testName2', 'testValue2')
- res.cookie('testName3', 'testValue3')
- res.send('<h1>hello world!</h1>')
- })

我們可以看到,Cookie 就是一段字符串。但這個字符串是有格式的,由鍵值對 key=value 構(gòu)成,鍵值對之間由一個分號和一個空格隔開。
什么是 Cookie
說了這么多,大家應(yīng)該知道 Cookie 是什么吧。整理一下有以下幾個點:
- Cookie 就是瀏覽器儲存在用戶電腦上的一小段文本文件
- Cookie 是純文本格式,不包含任何可執(zhí)行的代碼
- Cookie 由鍵值對構(gòu)成,由分號和空格隔開
- Cookie 雖然是存儲在瀏覽器,但是通常由服務(wù)器端進(jìn)行設(shè)置
- Cookie 的大小限制在 4kb 左右
Cookie 的屬性選項
每個 Cookie 都有一定的屬性,如什么時候失效,要發(fā)送到哪個域名,哪個路徑等等。在設(shè)置任一個 Cookie 時都可以設(shè)置相關(guān)的這些屬性,當(dāng)然也可以不設(shè)置,這時會使用這些屬性的默認(rèn)值。
expires / max-age
expires / max-age 都是控制 Cookie 失效時刻的選項。如果沒有設(shè)置這兩個選項,則默認(rèn)有效期為 session,即會話 Cookie。這種 Cookie 在瀏覽器關(guān)閉后就沒有了。
expires
expires 選項用來設(shè)置 Cookie 什么時間內(nèi)有效,expires 其實是 Cookie 失效日期。
expires 必須是 GMT 格式的時間(可以通過 new Date().toGMTString() 或者 new Date().toUTCString() 來獲得)
- app.get('/', (req, res) => {
- // 這個 Cookie 設(shè)置十秒后失效
- res.cookie('testName0', 'testValue0', {
- expires: new Date(Date.now() + 100000)
- })
- // 這個 Cookie 不設(shè)置失效時間
- res.cookie('testName1', 'testValue1')
- res.send('<h1>hello world!</h1>')
- })
上面的代碼服務(wù)器設(shè)置了兩個 Cookie,一個設(shè)置了失效刻,另外一個沒有設(shè)置,也就是默認(rèn)的失效時刻 session?,F(xiàn)在我們重啟服務(wù)并且刷新一下頁面。
現(xiàn)在響應(yīng)頭部已經(jīng)加上了響應(yīng)的設(shè)置失效時刻的字段了。在控制臺輸入下面的代碼。
- console.log(`現(xiàn)在的 cookie 是:${document.cookie}`)
- setTimeout(() => {
- console.log(`5 秒后的 cookie 是:${document.cookie}`)
- }, 5000)
- setTimeout(() => {
- console.log(`10 秒后的 cookie 是:${document.cookie}`)
- }, 10000)

所以,Cookie 的失效時刻到了之后,通過 document.cookie 就訪問不到這個 Cookie 了,當(dāng)然以后發(fā)送請求也不會再帶上這個失效的 Cookie 了。
max-age
expires 是 http/1.0 協(xié)議中的選項,在新的 http/1.1 協(xié)議中 expires 已經(jīng)由 max-age 選項代替,兩者的作用都是限制 Cookie 的有效時間。expires 的值是一個時間點 (Cookie 失效時刻 = expires),而 max-age 的值是一個以秒為單位時間段 (Cookie 失效時刻 = 創(chuàng)建時刻 + max-age)
- // 設(shè)置 max-age,就是設(shè)置從 cookie 創(chuàng)建的時刻算起
- // 再過多少秒 cookie 就會失效
- app.get('/', (req, res) => {
- res.cookie('testName0', 'testValue0', {
- // express 這個參數(shù)是以毫秒來做單位的
- // 實際發(fā)送給瀏覽器就會轉(zhuǎn)換為秒
- // 十秒后失效
- maxAge: 10000
- })
- res.cookie('testName1', 'testValue1')
- res.send('<h1>hello world!</h1>')
- })

優(yōu)先級
如果同時設(shè)置了 max-age 和 expires,以 max-age 的時間為準(zhǔn)。
- app.get('/', (req, res) => {
- res.cookie('name0', 'value0')
- res.cookie('name1', 'value1', {
- expires: new Date(Date.now() + 30 * 1000),
- maxAge: 60 * 1000
- })
- res.cookie('name2', 'value2', {
- maxAge: 60 * 1000
- })
- res.send('<h1>hello world!</h1>')
- })

domain 和 path
name、domain 和 path 可以標(biāo)識一個唯一的 Cookie。domain 和 path 兩個選項共同決定了 Cookie 何時被瀏覽器自動添加到請求頭部中發(fā)送出去。具體是什么原理請看 Cookie 的作用域和作用路徑 這個章節(jié)。
如果沒有設(shè)置這兩個選項,則會使用默認(rèn)值。domain 的默認(rèn)值為設(shè)置該 Cookie 的網(wǎng)頁所在的域名,path 默認(rèn)值為設(shè)置該 Cookie 的網(wǎng)頁所在的目錄。
secure
secure 選項用來設(shè)置 Cookie 只在確保安全的請求中才會發(fā)送。當(dāng)請求是 HTTPS 或者其他安全協(xié)議時,包含 secure 選項的 Cookie 才能被保存到瀏覽器或者發(fā)送至服務(wù)器。
默認(rèn)情況下,Cookie 不會帶 secure 選項(即為空)。所以默認(rèn)情況下,不管是 HTTPS 協(xié)議還是 HTTP 協(xié)議的請求,Cookie 都會被發(fā)送至服務(wù)端。
httpOnly
這個選項用來設(shè)置 Cookie 是否能通過 js 去訪問。默認(rèn)情況下,Cookie 不會帶 httpOnly 選項(即為空),客戶端是可以通過 js 代碼去訪問(包括讀取、修改、刪除等)這個 Cookie 的。當(dāng) Cookie 帶 httpOnly 選項時,客戶端則無法通過 js 代碼去訪問(包括讀取、修改、刪除等)這個 Cookie。
看看代碼吧,修改 main.js,保存重啟服務(wù),刷新頁面。
- app.get('/', (req, res) => {
- res.cookie('notHttpOnly', 'testValue')
- res.cookie('httpOnlyTest', 'testValue', {
- httpOnly: true
- })
- res.send('<h1>hello world!</h1>')
- })
看圖,設(shè)置了 httpOnly 的 Cookie 多了一個勾。而且通過 document.cookie 無法訪問到那個 Cookie。
在客戶端是不能通過 js 代碼去設(shè)置 一個 httpOnly 類型的 Cookie 的,這種類型的 Cookie 只能通過服務(wù)端來設(shè)置,發(fā)送請求的時候,我們看到請求頭還是會帶上這個設(shè)置了 httpOnly 的 Cookie,如下圖。

設(shè)置 Cookie
明確一點:Cookie 可以由服務(wù)端設(shè)置,也可以由客戶端設(shè)置??吹竭@里相信大家都可以理解了吧。
服務(wù)端設(shè)置 Cookie
看回剛剛的那張圖,我們設(shè)置了很多個 Cookie。

一個 Set-Cookie 字段只能設(shè)置一個 Cookie,當(dāng)你要想設(shè)置多個 Cookie,需要添加同樣多的 Set-Cookie 字段
服務(wù)端可以設(shè)置 Cookie 的所有選項:expires、domain、path、secure、HttpOnly
客戶端設(shè)置 Cookie
在網(wǎng)頁即客戶端中我們也可以通過 js 代碼來設(shè)置 Cookie。
設(shè)置
- document.cookie = 'name=value'
可以設(shè)置 Cookie 的下列選項:expires、domain、path,各個鍵值對之間都要用 ; 和 空格 隔開
- document.cookie='name=value; expires=Thu, 26 Feb 2116 11:50:25 GMT; domain=sankuai.com; path=/';
secure
只有在 https 協(xié)議的網(wǎng)頁中,客戶端設(shè)置 secure 類型的 Cookie 才能成功
HttpOnly
客戶端中無法設(shè)置 HttpOnly 選項
刪除 Cookie
Cookie 的 name、path 和 domain 是唯一標(biāo)識一個 Cookie 的。我們只要將一個 Cookie 的 max-age 設(shè)置為 0,就可以刪除一個 Cookie 了。
- let removeCookie = (name, path, domain) => {
- document.cookie = `${name}=; path=${path}; domain=${domain}; max-age=0`
- }
Cookie 的作用域和作用路徑
作用域
在說這個作用域之前,我們先來對域名做一個簡單的了解。
子域,是相對父域來說的,指域名中的每一個段。各子域之間用小數(shù)點分隔開。放在域名***的子域稱為***級子域,或稱為一級域,在它前面的子域稱為二級域。
以下圖為例,news.163.com 和 sports.163.com 是子域,163.com 是父域。
當(dāng) Cookie 的 domain 為 news.163.com,那么訪問 news.163.com 的時候就會帶上 Cookie;
當(dāng) Cookie 的 domain 為 163.com,那么訪問 news.163.com 和 sports.163.com 就會帶上 Cookie
作用路徑
當(dāng) Cookie 的 domain 是相同的情況下,也有是否帶上 Cookie 也有一定的規(guī)則。
在子路徑內(nèi)可以訪問訪問到父路徑的 Cookie,反過來就不行。
看看例子,還是先修改 main.js
- app.get('/parent', (req, res) => {
- res.cookie('parent-name', 'parent-value', {
- path: '/parent'
- })
- res.send('<h1>父路徑!</h1>')
- })
- app.get('/parent/childA', (req, res) => {
- res.cookie('child-name-A', 'child-value-A', {
- path: '/parent/childA'
- })
- res.send('<h1>子路徑A!</h1>')
- })
- app.get('/parent/childB', (req, res) => {
- res.cookie('child-name-B', 'child-value-B', {
- path: '/parent/childB'
- })
- res.send('<h1>子路徑B!</h1>')
- })
下面這里的 “域” 應(yīng)該改為路徑

