我正在参与掘金会员专属活动-源码共读第一期,点击参与
前言
从易到难开始学习源码。学习源码不是为了面试,只是想揭开别人造的轮子背后的神秘面纱。同时也希望在此过程中提升自己的能力和开拓视野。
使用 js-cookie
安装
npm install js-cookie
或
pnpm install js-cookie
使用
创建一个 cookie.js 文件,写一个简单的例子
创建一个 cookie,名字为 foo, 值为 ‘bar’
import Cookies from 'js-cookie'
Cookies.set('foo', 'bar')
获取 cookie 的值
Cookies.get('foo'); // bar
删除 cookie 的值
Cookies.remove('foo')
以上的 js-cookie 在使用上的基础语法,在创建或者删除的时候,js-cookie 中的 set 还提供了第三个参数 options。语法:Cookies.set(name: string, value, options)
options 参数配置
-
expires / max-age 设置 cookie 的过期时间
-
Size 设置cookie 的大小
-
path 设置cookie 的路径,默认是 “/”,本域名下所有页面都可以访问
-
domain 设置 cookie 的域(例子: .test.com )
-
secure 设置只能通过https来传递Cookie(默认为 false,如果设置成 true,那么只能通过http或者其他安全协议才能访问)
-
sameSite 防止 CSRF 攻击和用户追踪(可以设置三个值:Strict、Lax 和 None)
- Strict 最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。
- Lax 规则稍稍放宽,大多数情况下是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。
- None 关闭 sameSite 属性,提示必须设置 Secure 属性,否则无效
备注:以上是 cookie 的一部分参数,不是全部。
源码
克隆一份代码 js-cookies地址 到本地
阅读文件 src/api.mjs,大致的代码如下,然后逐个分析 set、get以及返回的对象
function init (converter, defaultAttributes) {
function set (name, value, attributes) {
...
}
function get (name) {
...
}
return Object.create(
...
)
}
export default init(defaultConverter, { path: '/' })
set 函数
function set (name, value, attributes) {
if (typeof document === 'undefined') {
return
}
attributes = assign({}, defaultAttributes, attributes)
// cookies过期时间
if (typeof attributes.expires === 'number') {
attributes.expires = new Date(Date.now() + attributes.expires * 864e5)
}
if (attributes.expires) {
attributes.expires = attributes.expires.toUTCString()
}
// 将name encodeURIComponent 编码
name = encodeURIComponent(name)
.replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent)
.replace(/[()]/g, escape)
// 循环attributes生成stringifiedAttributes
var stringifiedAttributes = ''
for (var attributeName in attributes) {
if (!attributes[attributeName]) {
continue
}
stringifiedAttributes += '; ' + attributeName
if (attributes[attributeName] === true) {
continue
}
// Considers RFC 6265 section 5.2:
// ...
// 3. If the remaining unparsed-attributes contains a %x3B (";")
// character:
// Consume the characters of the unparsed-attributes up to,
// not including, the first %x3B (";") character.
// ...
stringifiedAttributes += '=' + attributes[attributeName].split(';')[0]
}
// converter.write(value, name):使用 encodeURIComponent 解码
return (document.cookie =
name + '=' + converter.write(value, name) + stringifiedAttributes)
}
- 获取属性 attributes 对象
- 获取属性 expires (cookie 过期时间)转成 UTC String
- 将name encodeURIComponent 函数编码,防止 cookie 不受一些特殊字符的干扰
- 将 attributes(对象类型)转成 stringifiedAttributes(字符串类型,例如:“;expires=1;path=/”)
- 最后将name和value以及属性拼接成字符串并返回
get 函数
function get (name) {
if (typeof document === 'undefined' || (arguments.length && !name)) {
return
}
// To prevent the for loop in the first place assign an empty array
// in case there are no cookies at all.
// 获取所有的cookie信息,并转成数组类型
var cookies = document.cookie ? document.cookie.split('; ') : []
var jar = {}
// 循环 cookie,并将 cookie 转成 key:value
for (var i = 0; i < cookies.length; i++) {
var parts = cookies[i].split('=')
var value = parts.slice(1).join('=')
try {
var found = decodeURIComponent(parts[0])
jar[found] = converter.read(value, found)
if (name === found) {
break
}
} catch (e) {}
}
return name ? jar[name] : jar
}
- 获取所有的cookie信息,并转成数组类型
- 循环 cookie,将 cookie 转成 key:value,存入到 jar 对象中
- 在 jar 找到 name,返回 name 的值
init 返回的函数
return Object.create(
{
set: set,
get: get,
remove: function (name, attributes) {
set(
name,
'',
assign({}, attributes, {
expires: -1
})
)
},
withAttributes: function (attributes) {
return init(this.converter, assign({}, this.attributes, attributes))
},
withConverter: function (converter) {
return init(assign({}, this.converter, converter), this.attributes)
}
},
{
attributes: { value: Object.freeze(defaultAttributes) },
converter: { value: Object.freeze(converter) }
}
)
- remove 将 name 设置成空,设置成已过期
- 通过withAttributes这个函数,可以修改attributes
- 通过withConverter这个函数,可以修改converter
- set、get、remove、withAttributes、withConverter
最后
以上是我在学习 js-cookie 的知识点,我认为源码当中最巧妙的地方是在 init 函数返回时通过 object.create(),并且将修改 attributes和converter 设置两个函数。