- 本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
- 这是源码共读的第17期,链接:juejin.cn/post/708314…
了解源码之前先要了解此npm库是怎么用的:
// 从READNME.md中获取到使用方式,只列举了较为常用的方式
// 设置name的值为value,有效期为7天
Cookies.set('name', 'value', { expires: 7, path: '' })
// 获取name的值
Cookies.get('name')
// 获取cookie中的键值对
Cookies.get()
// 设置cookie在域名sub.example.com中可以被获取或共享
Cookies.get('foo', { domain: 'sub.example.com' })
// 删除对应的值
Cookies.remove('name')
看过了js-cookie的使用之后,对此库的大概用法或者实现方式其实就有了一个大概的了解了。下面正式的开始来了解它的实现:
import assign from './assign.mjs'
import defaultConverter from './converter.mjs'
// 初始化函数,返回一个对象主要是实现了set, get, remove方法
function init (converter, defaultAttributes) {
function set (name, value, attributes) {}
function get (name) {}
return Object.create({
set: set,
get: get,
remove: function (name, attributes) {},
withAttributes: function (attributes) {},
withConverter: function (converter) {}
},{
attributes: { value: Object.freeze(defaultAttributes) },
converter: { value: Object.freeze(converter) }
})
}
export default init(defaultConverter, { path: '/' })
上面的初始化函数是一个主要的结构框架,是此库的核心,下面一部分一部分的来进行分解。
首先进行set的讲解:
function set (name, value, attributes) {
// 判断document对象是否是有效的
if (typeof document === 'undefined') {
return
}
// assign函数其实是一个自己实现的合并函数,将对应的对象的属性合并到一个参数的第一个对象上
attributes = assign({}, defaultAttributes, attributes)
// 864e5为一天的毫秒数值
// 判断参数expires是否为数字,如果是数字则乘上一天,设置键对应的过期时间
if (typeof attributes.expires === 'number') {
attributes.expires = new Date(Date.now() + attributes.expires * 864e5)
}
// 此处也是过期时间的设置,不过是进行了转回而已
if (attributes.expires) {
attributes.expires = attributes.expires.toUTCString()
}
// 对cookie的key进行编码和正则的替换
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
}
// 先将attributeName赋值
stringifiedAttributes += '; ' + attributeName
// 去除有true的情况
if (attributes[attributeName] === true) {
continue
}
// 防止配置中有;的情况
stringifiedAttributes += '=' + attributes[attributeName].split(';')[0]
}
// 设置cookie
return (document.cookie =
name + '=' + converter.write(value, name) + stringifiedAttributes)
}
其实不是特别复杂,主要是先进行配置合并,对存储的时间进行校验,对存储的key进行编码和解码,最后设置对应的值到document.cookie中,最后还使用了一下converter.write(value, name)函数进行了值的转换,下面看一下这个函数具体是什么作用: 首先看一下代码的全貌,converter其实就是传入的defaultConverter,defaultConverter来自./converter.mjs,所以converter.write是文件converter.mjs中的功能,看一下write的功能,其实很简单就是对值进行了编码和替换,类似set中对name的处理
// ./converter.mjs
export default {
read: function (value) {
if (value[0] === '"') {
value = value.slice(1, -1)
}
return value.replace(/(%[\dA-F]{2})+/gi, decodeURIComponent)
},
write: function (value) {
return encodeURIComponent(value).replace(
/%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g,
decodeURIComponent
)
}
}
既然设置了cookie,那么自然会有获取cookie:
function get (name) {
if (typeof document === 'undefined' || (arguments.length && !name)) {
return
}
// 获得所有cookie并根据; 拆分成数组,如果不存在cookie就设置为空数组
var cookies = document.cookie ? document.cookie.split('; ') : []
var jar = {}
for (var i = 0; i < cookies.length; i++) {
// 根据=分割键值对
var parts = cookies[i].split('=')
// 上面分割键值对时有可能会出现值中有等号的情况,所以第一个parts中第一个值为key,后面的所有值为value
var value = parts.slice(1).join('=')
try {
// 将key进行解码
var found = decodeURIComponent(parts[0])
// 对value进行解密,并且组成key-value放入jar
jar[found] = converter.read(value, found)
// 如果取到对应的值后直接跳出
if (name === found) {
break
}
} catch (e) {}
}
// 如果存在name这返回对应的值,否则返回键值对jar(即全部值)
return name ? jar[name] : jar
}
converter.read函数在上面和 converter.write其实原理也是一样的,一个是解码一个是编码而已。
remove是用来删除对应的cookie值的,其实很巧妙的运用了set,将对应的值设置为空。 withAttributes和withConverter都是巧妙的利用了init函数,只不过其中的参数不同罢了。 withAttributes是用来更新 attributes 配置,并返回全新 Cookie 对象。 withConverter是用来更新 converter 配置,并返回全新 Cookie 对象。
并且考虑到了全局污染配置的问题,使用Object.freeze将attributes和converter进行了冻结,不可以改变,防止有人根据自己项目的要求把默认的配置给变动了,当然有时需要改变时,就要利用withAttributes和withConverter来进行修改。
总结
考虑细致,withAttributes和withConverter配合冻结属性,让代码即可以修改配置,又可以防止全局污染,并且代码大量的复用,减少了维护,并且代码很简洁。