前言
- 本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
- 这是源码共读的第17期,链接:第17期 | js-cookie
代码结构
源码地址:js-cookie
源码文件地址:js-cookie/src/api.mjs
function init (converter, defaultAttributes) {
function set (name, value, attributes) {
// ...设置cookie
}
function get (name) {
// ...获取cookie
}
function remove (name,attributes) {
// ...处理remove
}
function withAttributes (attributes) {
// ...属性拓展
}
function withConverter (converter) {
// ...自定义解码函数拓展
}
return Object.create({
set,
get,
remove,
withAttributes,
withConverter
}, {
attributes: { value: Object.freeze(defaultAttributes) },
converter: { value: Object.freeze(converter) }
})
}
export default init(defaultConverter, { path: '/' })
核心是提供 get(key)、set(key, value) 和 remove(key) 3 个 API,同时提供配置中心以支持属性修改和自定义解码函数
set
一个 cookie 不只有 key 和 value,还有 expires 过期时间以及 path 路径。一个完整的 cookie 应该长这样
${key}=${value}; expires=${expires}; path=${path}
这里不希望 set 函数的入参变得很冗余,所以这里的入参设计为:key, value, attributes 3 个。其中,attributes 是个对象,里面为 cookie 的属性:expires, path,同时为了方便使用设置了 defaultAttributes
先看代码:
function set (name, value, attributes) {
if (typeof document === 'undefined') {
return
}
attributes = assign({}, defaultAttributes, attributes)
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)
.replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent)
.replace(/[()]/g, escape)
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]
}
return (document.cookie =
name + '=' + converter.write(value, name) + stringifiedAttributes)
}
代码不是很难理解,首先对 expires 做了转成 UTC 时间戳的处理,然后把 attributes 拍扁成一个 string,最后追加到 ${key}=${value} 后面。
其中 new Date(Date.now() + attributes.expires * 864e5)的864e5是 24 小时的毫秒值,具体可见 Stackoverflow
name 也使用encodeURIComponent编码处理
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.
var cookies = document.cookie ? document.cookie.split('; ') : []
var jar = {}
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
}
get函数核心做了以下几件事
1、读取 document.cookie 然后再把 document.cookie转成数组
2、循环数组,处理每个 cookie,分离key 和 value,其中 key使用 decodeURIComponent 做了解码处理
3、cookie 的值有可能里会有 '=' 号,所以split('=') 后的,还要再 join('=') 一下变回原来的值。比如:a=123=456,join 后的 value 还是 123=456 而不是 123
4、返回结果,如果key没传则认为是获取cookie列表
编码与解码
为了 cookie 不受一些特殊字符的干扰,这里还要需要对 cookie 的值做编码与解码的工作。
cookie 的规范参考: RFC 2109 、 RFC 2965 、 RFC6265。
这里使用 encodeURIComponent 和 decodeURIComponent 做编码和解码的工作。
remove
这里就比较简单了,就是调用 set 处理即可
function (name, attributes) {
set(
name,
'',
assign({}, attributes, {
expires: -1
})
)
}
配置中心
为了方便操作expires以及支持自定义编解码函数(为了安全)需要提供初始化配置中心入口,于是这里暴露两个函数
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) }
}
总结
set函数:把attributes stringify,然后追加到key=value后,document.cookie = ${key}=${value}${attrStr}get函数:将document.cookie字符串转化为Object形式,转化过程中判断是否在存key,如果有就返回对应valueremove函数:调用set,把expires设置为 -1 天,cookie直接过期被删除- 默认使用
encodeURIComponent和decodeURIComponent处理编解码问题 withAttributes自定义属性配置,并返回全新的操作Cookie的对象withConverter自定义解码函数配置,并返回全新的操作Cookie的对象- 为了解决全局污染的问题,需要把
attributes和converter两个配置存到函数参数里,并且通过withAttributes和withConverter调用init返回新Cookie对象