js-cookie 源码深度解析:从使用到实现原理
本文深入解析前端常用Cookie操作库js-cookie的源码实现。文章从基础使用入手,详细拆解了set、get、remove等核心方法的代码逻辑,包括参数合并、时间处理、字符串编码及Cookie字符串的拼接与解析机制。通过阅读约百行源码,帮助开发者理解其设计思想及字符串处理技巧,为前端数据存储提供实用参考。
引言
在前端开发中,直接操作Cookie的场景相对少见,且由于兼容性和存储限制等问题,Cookie的使用正逐渐减少。尽管如此,我们仍可通过不使用原生Cookie的方式,学习其背后的设计思想,或通过本次源码分析掌握其他相关技术知识。
本文重点介绍:js-cookie
源码分析
使用
参考README文档,我们可以了解js-cookie的基本用法:
// 设置
Cookies.set('name', 'value');
// 设置过期时间
Cookies.set('name', 'value', { expires: 7 })
// 获取
Cookies.get('name') // => 'value'
// 获取所有
Cookies.get() // => { name: 'value' }
// 获取指定域名下
Cookies.get('foo', { domain: 'sub.example.com' })
// 删除
Cookies.remove('name')
此外还有更多配置选项和使用说明,建议读者查阅官方文档自行探索。
源码
js-cookie的源码量不大,位于src目录下的api.mjs文件即为核心源码,代码量仅约一百行。
/* eslint-disable no-var */
import assign from './assign.mjs'
import defaultConverter from './converter.mjs'
function init (converter, 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)
}
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
}
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) }
}
)
}
export default init(defaultConverter, { path: '/' })
/* eslint-enable no-var */
分析
js-cookie的源码简洁,我们先观察其导出内容:
export default init(defaultConverter, { path: '/' })
这里直接导出了init函数的返回值,接下来分析init函数的具体实现:
function init (converter, defaultAttributes) {
// ...
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) }
}
)
}
该函数通过Object.create创建了一个对象,该对象包含set、get、remove、withAttributes、withConverter这几个方法。这些方法均定义在init函数内部,下面逐一查看其实现细节:
set
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)
}
逐行分析如下:
if (typeof document === 'undefined') {
return
}
首先检查document对象是否存在,若不存在则直接返回,这表明js-cookie仅能在浏览器环境中运行。
attributes = assign({}, defaultAttributes, attributes)
接着合并配置项,将defaultAttributes与传入的attributes进行合并,这里的assign可简单理解为Object.assign。
if (typeof attributes.expires === 'number') {
attributes.expires = new Date(Date.now() + attributes.expires * 864e5)
}
if (attributes.expires) {
attributes.expires = attributes.expires.toUTCString()
}
随后判断expires是否为数字,若是,则将其转换为一个Date对象;
其中864e5是一个常量,末尾的e5表示后续添加5个0,即86400000代表一天的毫秒数。
接着判断expires是否存在,若存在则将其转换为UTC时间格式。
name = encodeURIComponent(name)
.replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent)
.replace(/[()]/g, escape)
此处对name进行了编码,并对name中的%、(、)进行了转义处理。
escape是一个内置函数,用于将字符串转换为UTF-8编码的字符串,这里的escape作用是将(、)转换为%28、%29。
参考: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]
}
此处将attributes转换为字符串,其中stringifiedAttributes为字符串变量,最终结果如下:
stringifiedAttributes = '; path=/; expires=Wed, 21 Oct 2015 07:28:00 GMT'
最后将name、value、stringifiedAttributes拼接起来,并赋值给document.cookie。
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方法的实现较为简单,主要解析document.cookie,并将其转换为对象,下面逐行解析:
if (typeof document === 'undefined' || (arguments.length && !name)) {
return
}
与set方法相比,这里多了一个(arguments.length && !name)的判断,旨在防止传入空字符串的name。
var cookies = document.cookie ? document.cookie.split('; ') : []
此处将document.cookie分割为数组,每一项代表一个cookie。
var jar = {}
for (var i = 0; i < cookies.length; i++) {
var parts = cookies[i].split('=')
var value = parts.slice(1).join('=')
}
此步骤仅提取cookie的name和value,忽略其他额外附加信息。
try {
var found = decodeURIComponent(parts[0])
jar[found] = converter.read(value, found)
if (name === found) {
break
}
} catch (e) {
}
此处对name进行解码,并将name和value存储到jar对象中,若传入了name,则在找到对应的name后跳出循环。
return name ? jar[name] : jar
最后返回jar对象,若传入了name,则返回对应的value,否则返回整个jar对象。
核心在于converter.read,该方法用于解析value,其中converter是一个对象,包含两个方法:
/* eslint-disable no-var */
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
)
}
}
/* eslint-enable no-var */
read方法用于对value进行解码,write方法用于对value进行编码。
remove
function remove(name, attributes) {
set(
name,
'',
assign({}, attributes, {
expires: -1
})
)
}
remove方法通过调用set将value设置为空字符串,并将expires设置为-1,从而实现删除cookie的效果。
withAttributes & withConverter
Object.create({
withAttributes: function (attributes) {
return init(assign({}, defaultAttributes, attributes))
},
withConverter: function (converter) {
return init(assign({}, defaultConverter, converter))
}
})
这两个方法用于设置defaultAttributes和defaultConverter,这两个对象分别用于设定cookie的默认属性和默认的converter。
总结
通过研读js-cookie的源码,我们可以掌握cookie的基本用法。若需深入了解cookie,建议参考MDN文档。
同时,我们也学习了许多字符串处理方法,如encodeURIComponent、decodeURIComponent、split、join等。
以上就是JS前端操作 Cookie源码示例解析的详细内容,更多关于JS前端操作Cookie的资料请关注本站其它相关文章!
JS处理含Emoji或中文的字符串转数组乱码问题
2026-11-05 16:12:28
JS怎么使用展开运算符合并对象
2026-11-05 14:42:18
ES6+环境下判断JS对象为空的简洁写法
2026-11-05 12:12:23
怎么通过replacer函数自定义JS中JSON的格式化输出
2026-11-05 11:19:44
JS正则表达式基础:字面量与RegExp构造函数的区别及常用元字符
2026-11-05 11:16:58
