js-cookie源码了解

163 阅读4分钟

了解源码之前先要了解此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配合冻结属性,让代码即可以修改配置,又可以防止全局污染,并且代码大量的复用,减少了维护,并且代码很简洁。