cookie知识总结

最近,清除cookie的时候,使用了document.cookie = '',而后就发现没有做用。因而,带着上述疑问,找个时间研究了下cookie。本文主要是基于我遇到问题,而后解决问题的思路写的,并非以从0到1讲述cookie的思路写的。html

为了解决上述问题,以及在解决问题中的发散思考,主要经过如下问题:算法

  1. document.cookie = ''可否用来清除cookie;
  2. 同时设置了expires和max-age,哪一个有效;
  3. 假设已经有一个名为hello的cookie,再次设置名为hello的cookie是新增仍是修改
  4. 如何把字符串形式的cookie改为json的形式;
  5. 若是设置了多个同名cookie,获取cookie时以哪一个为准;

学习了如下知识点:json

  1. cookie赋值语法;
  2. cookie赋值算法;
  3. cookie取值算法。

预备知识

本文参考了英文文档,因此提早说明如下英文对应的中文翻译:segmentfault

  1. cookie store: cookie表,浏览器内部用于存储cookie信息的数据结构;
  2. cookie name: 一个cookie的名称;
  3. cookie value: 一个cookie的值;
  4. cookie attribute: 一个cookie的属性,cookie除了基本的名称和值以外,还有用来进一步描述cookie的属性,好比Expires,Max-Age,Domain,Path,Secure HttpOnly等。这些属性是提供给用户设置cookie的时候使用的,并非cookie内部实际存储的属性。

其余预备知识:数组

每一个cookie在实际存储的时候,会有下述字段:name, value, expiry-time, domain, path, creation-time, last-access-time, persistent-flag, host-only-flag, secure-only-flag, and http-only-flag。能够看出这些字段和设置cookie时的属性并非一对一关系,其中,浏览器

  1. expiry-time是经过Expires和Max-Age得出来的;
  2. persistent-flag和是否设置了过时时间有关;
  3. creation-time(建立时间)和last-access-time(最后一次访问时间)是浏览器自动存储的属性。

document.cookie赋值语法

这部份内容主要是基于如下问题:cookie

  1. document.cookie = ''可否用来清除cookie?

首先看下document.cookie的定义:session

The Document property cookie lets you read and write cookies associated with the document. It serves as a getter and setter for the actual values of the cookies.

从上面能够看到,document.cookie只是提供了一个getter和setter方法来访问和设置cookie,并非直接修改cookie的值。什么意思呢?数据结构

咱们知道,JS提供了两种类型的属性,data propertyaccessor property。分别举个例子对比看下:dom

// data property
var obj1 = {
    hello: 'name'
}
Object.getOwnPropertyDescriptor(obj1, 'hello')

image.png

// accessor property
var realHello = '' // 实际存储obj2.hello的变量
var obj2 = {
    get hello () {
        return 'custom get ' + realHello
    },
    set hello (val) {
        realHello = 'custom set ' + val
    }
}
obj2.hello = 'name'

console.log(obj2.hello)
Object.getOwnPropertyDescriptor(obj2, 'hello')

image.png

能够看到用于描述obj1hello属性的是valuewritable,用于描述obj2hello属性的是getset。简单的说,获取和修改data property是直接的,获取和修改accessor property是间接的,能够经过该属性的getset作自定义处理。咱们熟悉的Vue 2.0响应式的data的属性就是accessor property

document的cookie属性也是accessor property。验证的时候发现一个有意思的问题,最终是在document的原型上找到的属性描述符,之后有时间了研究下:

Object.getOwnPropertyDescriptor(document, 'cookie') // undefined
Object.getOwnPropertyDescriptor(document.__proto__.__proto__, 'cookie')

image.png

咱们看下document.cookie的语法:

document.cookie = newCookie;

修改的时候,newCookie的格式须要知足以下形式,且一次只能设置/更新单个cookie:

In the code above, newCookie is a string of form key=value. Note that you can only set/update a single cookie at a time using this method.

举个例子,打开MDN document.cookie页面。添加一个cookie,每一个cookie后面能够添加和该cookie相关的一系列属性,经过分号;分割:

// cookie默认过时时间是session,也就是浏览器关闭的时候,该cookie就失效了
document.cookie = 'hello1=world1;'

// 使用max-age设置过时时间:max-age以秒为单位,设置1个小时以后过时:3600 = 60 * 60
document.cookie = 'hello2=world2;max-age=3600'

// 使用expires设置过时时间:expires是GMT形式的日期格式,设置1小时以后过时
var current = new Date()
current.setTime(current.getTime() + 3600000) // setTime单位是ms,3600000 = 60 * 60 * 1000
document.cookie = `hello3=world3;expires=${current.toUTCString()}`

Chrome Application Cookies内容截图以下:
image.png

既然document.cookie只能设置/更新单个cookie,当赋值为空字符串的时候,那就是什么都没有作。也就是document.cookie = ''不会产生任何做用,不能用于清除cookie。

那么,
问:如何使用document.cookie清除一个cookie呢?
答:能够在设置cookie的时候,设置一个已通过去的过时时间:

// 使用expires设置一个过去一小时以前的时间
document.cookie = 'hello2=world2;max-age=-3600'

// 使用expires设置一个过去一小时以前的时间
var current = new Date()
current.setTime(current.getTime() - 3600000)
document.cookie = `hello3=world3;expires=${current.toUTCString()}`

如今,只剩下了一个hello1:
image.png

注:浏览器开发者工具是提供了清除cookie的操做的,上述主要讨论的是如何经过js清除cookie。

cookie赋值算法

这部份内容主要是基于如下两个问题:

  1. 同时设置了expires和max-age,哪一个有效?
  2. 假设已经有一个名为hello的cookie,再次设置名为hello的cookie的时候是新增仍是修改?

问题1: 同时设置了expires和max-age,哪一个有效?

前面的例子中,我使用了expires和max-age,个人疑问是感受这两个属性是干了同样的事情,若是我同时设置了这两个属性的话,哪一个生效呢?

带着这个疑问,找到了RFC 6265 - HTTP State Management Mechanism。这个规范里面明肯定义了如何设置/更新cookie。

就下面的例子,咱们按照文档找一下答案:

var current = new Date()
console.log('now: ', current.toUTCString()) // now:  Sat, 20 Feb 2021 11:41:02 GMT
current.setTime(current.getTime() + 3600000)
// 使用expires设置为一小时以后过时:60 * 60 * 1000 = 3600000ms
// 使用max-age设置为两小时以后过时:2 * 60 * 60 = 7200s
document.cookie = `hello=world;expires=${current.toUTCString()};max-age=7200` // hello=world;expires=Sat, 20 Feb 2021 12:41:02 GMT;max-age=7200

这部分的内容主要是在第五部分的5.2。首先,它会把设置的这个值经过逗号分割成多个部分,而后经过等号把每一个部分分红key-value的形式:

hello=world;expires=Sat, 20 Feb 2021 12:41:02 GMT;max-age=7200
// 变为
hello=world // 第一个分号前面的是这个cookie的name和value,后面的是这个cookie的属性
expires=Sat, 20 Feb 2021 12:41:02 GMT // 属性
max-age=7200 // 属性
// 变为
hello: world
expires: expires=Sat, 20 Feb 2021 12:41:02 GMT
max-age: 7200

而后看每一个属性,文档中5.2.*部分,算法会解析每一个key-value,并放在一个cookie属性列表cookie-attribute-list里面,分析属性的时候不区分大小写,expires和max-age转化为:

Expires: expires=Sat, 20 Feb 2021 12:41:02 GMT
Max-Age: expires=Sat, 20 Feb 2021 13:41:02 GMT

属性都分析完以后,咱们能够获得和每条cookie相关的三个部分:cookie-name(cookie名),cookie-value(cookie值),cookie-attribute-list(cookie属性列表)。而后进入设置阶段,关键在5.3部分的第三步:
image.png
首先看上面生成的cookie属性列表里面是否包含Max-Age,若是包含,把cookie的过时时间设置成最后一个Max-Age的值;若是没有Max-Age,才会查看是否包含Expires,若是包含,把cookie的过时时间设置成最后一个Expires的值。这里last attribute的意思是咱们在给cookie赋值的时候,是能够写多个max-age的,这个时候取最后一个语法有效的max-age。

因此,简单的说:当expires和max-age同时出现的时候,max-age的优先级更高。

Chrome截图也证实了max-age优先级更高:
image.png

假设已经有一个名为hello的cookie,再次设置名为hello的cookie是新增仍是修改?

咱们接着往下看5.3部分的第十一、12步:
image.png
在11步中,首先判断已有cookie中是否有和本cookie同时具备相同的name(名称)domain(域)path(路径)的cookie。
11.1 若是有,标记为old-cookie;
11.2 若是新cookie不是经过http的方式设置的,而且old-cookie设置了只能用于http,忽略新cookie;
11.3 把新cookie的creation-time(建立时间)字段更新为old-cookie的建立时间;
11.4 从cookie表中移除old-cookie;
12 把新cookie插入到cookie表里面。

因此,能够得出:判断是新增仍是修改的标准是cookie表中是否存在和新cookie的name,domain和path属性都同样的cookie。

在设置cookie的时候,若是设置结果和你想要的不符,能够按照上面例子的思路,查看RFC 6265 - HTTP State Management Mechanism文档的5.2和5.3部分。

cookie取值算法

这部份内容主要是基于如下两个问题:

  1. 如何把字符串形式的cookie改为json的形式?
  2. 若是设置了多个同名cookie,获取cookie时以哪一个为准?

如何把字符串形式的cookie改为json的形式?

之因此会遇到这个问题,主要是解决下面这个场景:
若是经过WebSocket发送请求,在链接创建的时候,是能够获取cookie信息的。可是,一旦链接创建成功,WebSocket一直链接的时候,发送的数据是不会携带cookie信息的。若是在链接创建以后,用户作了登出操做,这个时候其实不该该再响应用户请求,而且断开链接。

这时,问题来了,如何判断当用户登出的时候再也不响应用户请求呢?
其实和http同样,每次请求带上cookie信息就能够了。只不过发送http请求的时候,浏览器帮咱们作了这部分工做。WebSocket的话,就得咱们手动添加上了。

咱们能够经过document.cookie获取cookie值,可是该值是一个表示全部cookie的字符串。为了获取某个cookie的值,咱们就得本身解析。要想解析,就得知道cookie字符串的取值规则。这部份内容主要是RFC 6265 - HTTP State Management Mechanism文档的5.4部分

image.png

  1. 经过步骤4的第2步,能够知道不一样的cookie是经过分号和空格; 拼接起来的;
  2. 经过步骤4的第1步,能够知道最终结果中只会出现cookie的name和value,cookie的属性并不会出现。name和value是经过等号=拼接起来的;

因此,解析cookie的为代码能够表示以下:

var cookieStr = document.cookie
var cookieArray = cookieStr.split('; ') // 得到每一个cookie的字符串形式组成的数组
var cookieObj = {}
cookieArray.forEach(item => {
    var index = item.indexOf('=') // 获取每一个cookie字符串的key和value
    if (index !== -1) { // 没有使用split的缘由是value里面也是能够包含=号的
        cookieObj[item.slice(0, index)] = item.slice(index + 1)
    }
})
console.log(cookieObj)

若是设置了多个同名cookie,获取cookie时以哪一个为准?

例如,有下面几个cookie:

document.cookie = 'hello4=world4;'
document.cookie = 'hello5=world;'
document.cookie = 'hello5=worldShortPath;Path=/en-US'
document.cookie = 'hello6=world6;'

Chrome截图以下:
image.png

这部份内容仍是在RFC 6265 - HTTP State Management Mechanism文档的[5.4部分],只不过是在第2步。首先第1步是从cookie store(cookie列表)中找到和请求地址相关的全部cookie。而后执行第2步:
image.png

  1. 经过步骤2的第1步,能够知道具备更长path属性的cookie是放在前面的;
  2. 经过步骤2的第2步,能够知道path属性长度相同的时候,creation-times建立时间越早的放在前面。

综上,全部同名的cookie都会出如今最终的cookie字符串中。而且,cookie是先按照path属性就行排序,而后按照creation-times建立时间属性进行排序。可是,步骤二下面有注释,这种排序方式是实践得出的比较好的排序方式,可是浏览器不必定按照这种排序方式实现。

就上面的例子,咱们在设置完cookie以后,在控制台中查看document.cookie:

console.log(document.cookie) // hello4=world4; hello5=world; hello6=world6; hello5=worldShortPath

能够看到hello5的路径最短,因此放在最后,其余的按照设置顺序,也就是建立时间排序。

总结

在cookie赋值语法部分,咱们知道document.cookie = ''不能用来清除cookie;
在cookie赋值算法部分,咱们知道:

  1. 同时设置了expires和max-age,max-age有效;
  2. 假设已经有一个名为hello的cookie,再次设置名为hello的cookie的时候,若是name,domain和path属性彻底相同就是修改,不然是新增;

在cookie取值算法部分,咱们知道:

  1. 如何把字符串形式的cookie改为json的形式的时候,能够先经过; 分割一次,在经过=分割一次;
  2. 若是设置了多个同名cookie,全部同名的cookie都会出如今最终的cookie字符串中,按照path属性的长度和creation-times排序。

若是你遇到和cookie赋值语法、赋值算法和取值算法相关的问题,能够参照上述部分遇到的问题的解决思路去看看可否解决问题。

若有错误,欢迎留言讨论。

参考连接

  1. RFC 6265 - HTTP State Management Mechanism
  2. MDN document.cookie
相关文章
相关标签/搜索