乐趣区

关于javascript:cookie知识总结

最近,革除 cookie 的时候,应用了document.cookie = '',而后就发现没有作用。于是,带着上述疑难,找个工夫钻研了下 cookie。本文次要是基于我遇到问题,而后解决问题的思路写的,并不是以从 0 到 1 讲述 cookie 的思路写的。

为了解决上述问题,以及在解决问题中的发散思考,次要通过以下问题:

  1. document.cookie = ''是否用来革除 cookie;
  2. 同时设置了 expires 和 max-age,哪个无效;
  3. 假如曾经有一个名为 hello 的 cookie,再次设置名为 hello 的 cookie 是 新增 还是 批改
  4. 如何把字符串模式的 cookie 改成 json 的模式;
  5. 如果设置了多个同名 cookie,获取 cookie 时以哪个为准;

学习了以下知识点:

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

准备常识

本文参考了英文文档,所以提前阐明以下英文对应的中文翻译:

  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 赋值语法

这部分内容次要是基于以下问题:

  1. document.cookie = ''是否用来革除 cookie?

首先看下 document.cookie 的定义:

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。别离举个例子比照看下:

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

// 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')

能够看到用于形容 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')

咱们看下 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 内容截图如下:

既然 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:

注:浏览器开发者工具是提供了革除 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 局部的第三步:

首先看下面生成的 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 优先级更高:

假如曾经有一个名为 hello 的 cookie,再次设置名为 hello 的 cookie 是新增还是批改?

咱们接着往下看 5.3 局部的第 11、12 步:

在 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 局部。

  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 截图如下:

这部分内容还是在 RFC 6265 – HTTP State Management Mechanism 文档的[5.4 局部],只不过是在第 2 步。首先第 1 步是从 cookie store(cookie 列表)中找到和申请地址相干的所有 cookie。而后执行第 2 步:

  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
退出移动版